由于工作需要,最近对tomcat的日志进行了一些研究,发现其日志大致可以分为两类,一类是运行日志,即平常我们所说的catalina.out日志,由tomcat内部代码调用logger打印出来的;另一类是accesslog访问日志,即记录外部请求访问的信息。处理这两类日志,tomcat默认采用了不同的方式,运行类日志默认采用的是java.util.logging框架,由conf下的logging.properties负责配置管理,也可以支持切换到log4j2(具体可参看我的前一篇博文:升级tomcat7的运行日志框架到log4j2 );对于访问日志,tomcat默认是按日期直接写进文件,由server.xml中配置Valve来管理。

    默认情况下,Valve是打开的,在server.xml中我们可以找到如下配置:
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" pattern="%h %l %u %t &quot;%r&quot; %s %b" prefix="localhost_access_log." suffix=".txt"/>

此配置会在logs下生成一个localhost_access_log.日期.txt,里面记录每次外部访问的一些信息,信息的内容是根据pattern来配置的,%后加不同的字母表示不同的信息,如上述默认的pattern配置会记录“访问端ip 用户名 时间 第一行请求内容 http状态码 发送字节大小”等内容,详细配置细节可以参考tomcat的accelog(url:https://tomcat.apache.org/tomcat-7.0-doc/config/valve.html#Access_Logging  )

通过分析AccessLogValve代码,其内部用的java方法操作文件处理代码:
@Override
public void log(Request request, Response response, long time) {
if (!getState().isAvailable() || !getEnabled() || logElements == null
|| condition != null
&& null != request.getRequest().getAttribute(condition)
|| conditionIf != null
&& null == request.getRequest().getAttribute(conditionIf)) {
return;
}
/**
* XXX This is a bit silly, but we want to have start and stop time and
* duration consistent. It would be better to keep start and stop
* simply in the request and/or response object and remove time
* (duration) from the interface.
*/
long start = request.getCoyoteRequest().getStartTime();
Date date = getDate(start + time);
// 字符缓冲区
CharArrayWriter result = charArrayWriters.pop();
if (result == null) {
result = new CharArrayWriter(128);
}
// pattern里不同的%表示不同的logElement,此处用result收集所有logElement里追加的内容
for (int i = 0; i < logElements.length; i++) {
logElements[i].addElement(result, date, request, response, time);
}
// 写文件将result写入
log(result);
if (result.size() <= maxLogMessageBufferSize) {
result.reset();
charArrayWriters.push(result);
}
}

其中log(result)实现如下:

@Override
public void log(CharArrayWriter message) {
// 每个一秒检查一下是否需要切换文件
rotate();
// 如果存在文件,先关闭再重新打开一个新日期的文件
if (checkExists) {
synchronized (this) {
if (currentLogFile != null && !currentLogFile.exists()) {
try {
close(false);
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
log.info(sm.getString("accessLogValve.closeFail"), e);
}
/* Make sure date is correct */
dateStamp = fileDateFormatter.format(
new Date(System.currentTimeMillis())); open();
}
}
}
// Log this message 同步加锁写入日志文件,此处使用了buffer
try {
synchronized(this) {
if (writer != null) {
message.writeTo(writer);
writer.println("");
if (!buffered) {
writer.flush();
}
}
}
} catch (IOException ioe) {
log.warn(sm.getString(
"accessLogValve.writeFail", message.toString()), ioe);
}
}

  通过上述核心代码可以看到,默认的tomcat是利用缓冲写文件的方式进行访问日志记录的,如果需要分析访问日志,比如找出一天内有多少过ip访问过,或者某一个ip在一分钟内访问了多少次,一般的处理方式是读取accesslog文件内容并进行分析,这么做一方面是无法满足实时分析的目的,更重要的数据量大的时候会严重影响分析效率,因此我们需要对其进行扩展,比如我们可以把访问日志打到kafka或mango中。

  tomcat 8之前版本的扩展相比于8及以后的版本有点麻烦,因为从tomcat 8以后,把accesslog专门提取了一个抽象类,负责根据pattern来组装内容,并留出了log(CharArrayWriter message)抽象方法用于扩展,开发只要扩展重写此方法即可,但8以前的版本需要自己继承ValveBase并实现AccessLog接口,重写log(Request request, Response response, long time)方法,由于作者所在的公司目前线上使用的是tomcat7 ,因此下面主要讲述如何在tomcat 7下进accesslog日志扩展进kafka。
扩展的步骤:
  1. 创建LeKafkaAccesslogValve继承ValveBase并实现AccessLog接口:

    @Override
    public void log(Request request, Response response, long time) {
    if (producerList != null && getEnabled() && getState().isAvailable() && null != this.accessLogElement) {
    try {
    getNextProducer().send(new ProducerRecord<byte[], byte[]>(topic, this.accessLogElement.buildLog(request,response,time,this).getBytes(StandardCharsets.UTF_8))).get(timeoutMillis, TimeUnit.MILLISECONDS);
    } catch (InterruptedException | ExecutionException | TimeoutException e) {
    log.error("accesslog in kafka exception", e);
    }
    }
    }
  2. 处理可配的参数
    private String topic;
    private String bootstrapServers; // If set to zero then the producer will not wait for any acknowledgment from the server at all.
    private String acks; private String producerSize ; private String properties; private List<Producer<byte[], byte[]>> producerList;
    private AtomicInteger producerIndex = new AtomicInteger(0);
    private int timeoutMillis;
    private boolean enabled = true; // 默认配置问true,即打入kafka,除非有异常情况或主动设置了。 private String pattern;
    private AccessLogElement accessLogElement;
    private String localeName;
    private Locale locale = Locale.getDefault();
  3. 根据不同的pattern配置解析出需要打印的内容(此部分tomcat8 已经在AbstractAccessLogValve中抽取出来)
    public static AccessLogElement parsePattern(String pattern) {
    final List<AccessLogElement> list = new ArrayList<>();
    boolean replace = false;
    StringBuilder buf = new StringBuilder();
    for (int i = 0; i < pattern.length(); ++i) {
    char ch = pattern.charAt(i);
    if (replace) {
    if ('{' == ch) {
    StringBuilder name = new StringBuilder();
    int j = i + 1;
    for (; (j < pattern.length()) && ('}' != pattern.charAt(j)); ++j) {
    name.append(pattern.charAt(j));
    }
    if (j + 1 < pattern.length()) {
    ++j;
    list.add(createAccessLogElement(name.toString(), pattern.charAt(j)));
    i = j;
    } else {
    list.add(createAccessLogElement(ch));
    }
    } else {
    list.add(createAccessLogElement(ch));
    }
    replace = false;
    } else if (ch == '%') {
    replace = true;
    list.add(new StringElement(buf.toString()));
    buf = new StringBuilder();
    } else {
    buf.append(ch);
    }
    }
    if (buf.length() > 0) {
    list.add(new StringElement(buf.toString()));
    }
    return new AccessLogElement() {
    @Override
    protected String buildLog(Request request, Response response, long time, AccessLog accesslog) {
    StringBuilder sBuilder = new StringBuilder(30);
    for (AccessLogElement accessLogElement : list) {
    sBuilder.append(accessLogElement.buildLog(request, response, time, accesslog));
    }
    return sBuilder.toString();
    }
    };
    }
  4. 在server.xml中增加配置
    <Valve className="com.letv.shop.lekafkavalve.LeKafkaAccesslogValve" enabled="true"  topic="info" pattern="%{yyyy-MM-dd HH:mm:ss}t||info||AccessValve||Tomcat||%A||%a||%r||%s||%D" bootstrapServers="kafka地址" producerSize="5" properties="acks=0||producer.size=3"/>

    tomcat8及以后版本的扩展要方便的多,直接继承AbstractAccessLogValve并重写log方法。

 
附件是kafka实现的源码下载
 
 
 
 
 

tomcat accesslog日志扩展的更多相关文章

  1. 配置spring boot 内置tomcat的accessLog日志

    #配置内置tomcat的访问日志server.tomcat.accesslog.buffered=trueserver.tomcat.accesslog.directory=/home/hygw/lo ...

  2. Tomcat AccessLog 格式化

    有的时候我们要使用日志分析工具对日志进行分析,需要对日志进行格式化,比如,要把accessLog格式化成这样的格式 c-ip s-ip x-InstancePort date time-taken x ...

  3. spring boot Tomcat访问日志

    1.Tomcat设置访问日志 <Host name="localhost" appBase="webapps" unpackWARs="true ...

  4. Tomcat中日志组件

    Tomcat日志组件 AccessLog接口 public interface AccessLog { public void log(Request request, Response respon ...

  5. Tomcat 修改日志输出配置 定期删除日志

    tomcat的下的日志catalina.out 和 qc.log疯狂增长,以下是解决办法 我生产环境tomcat版本 Server version: Apache Tomcat/7.0.35 Serv ...

  6. Tomcat各种日志的关系与catalina.out文件的分割

    Tomcat 各日志之间的关系 一图胜千言! 其他日志如localhost.{yyyy-MM-dd}.log.localhost-access.{yyyy-MM-dd}.log是context的名称, ...

  7. shell脚本切割tomcat的日志文件

    鉴于在调试logback和log4j的文件切割一直无法成功,随性用shell写个脚本用来切割tomcat下的日志文件(大家如果有在logback或log4j使用文件切割成功的话,可以留下使用方式,先谢 ...

  8. Tomcat访问日志详细配置

    在server.xml里的<host>标签下加上 <Valve className="org.apache.catalina.valves.AccessLogValve&q ...

  9. tomcat详细日志配置

    在server.xml里的<host>标签下加上<Valve className="org.apache.catalina.valves.AccessLogValve&qu ...

随机推荐

  1. Unity 特殊文件夹 : 位置不能随便放

    有以下几个文件夹: Assets 用来存放资源的文件夹,包括各种材质.模型等 Editor 编辑器类等脚本 Editor Default Resources Editor scripts can ma ...

  2. 深入理解闭包系列第三篇——IIFE

    × 目录 [1]实现 [2]用途 前面的话 严格来讲,IIFE并不是闭包,因为它并不满足函数成为闭包的三个条件.但一般地,人们认为IIFE就是闭包,毕竟闭包有多个定义.本文将详细介绍IIFE的实现和用 ...

  3. java笔试题

    下面有关JVM内存,说法错误的是? A.程序计数器是一个比较小的内存区域,用于指示当前线程所执行的字节码执行到了第几行,是线程隔离的 B.Java方法执行内存模型,用于存储局部变量,操作数栈,动态链接 ...

  4. c++面试常用知识(sizeof计算类的大小,虚拟继承,重载,隐藏,覆盖)

    一. sizeof计算结构体 注:本机机器字长为64位 1.最普通的类和普通的继承 #include<iostream> using namespace std; class Parent ...

  5. HTTP的RST包与WinHttp延迟关闭TCP连接

    一.RST包也常见于断开TCP连接  几个月前用wireshark抓HTTP包发现有的网络通信在结束的时候没有使用四次握手,而是直接使用RST包.如: 在TCP协议中RST表示复位,用来异常的关闭连接 ...

  6. Web API接口之Geolocation

    0.关于Geolocation Geolocation,地理位置API.用于获取用户的位置信息.它不算是现有的HTML5标准的“直系”成员,但是是W3C的一个标准.它几乎就是一个真正的JavaScri ...

  7. 应用程序框架实战二十二 : DDD分层架构之仓储(层超类型基础篇)

    前一篇介绍了仓储的基本概念,并谈了我对仓储的一些认识,本文将实现仓储的基本功能. 仓储代表聚合在内存中的集合,所以仓储的接口需要模拟得像一个集合.仓储中有很多操作都是可以通用的,可以把这部分操作抽取到 ...

  8. 【记录】ASP.NET MVC View 移动版浏览的奇怪问题

    ASP.NET MVC View 中的一段代码: <span id="span_Id">@Model.ID</span> 没什么问题吧,浏览器浏览正常,查看 ...

  9. SQLite关系型数据库

    SQLiteOpenHelper的使用:       首先声明一个DatabaseHelper类,这个类继承于SQLiteOpenHelper类,首先得有构造函数,声明DatabaseHelper类如 ...

  10. 3分钟干货学会使用node-inspector调试NodeJS代码

    使用node-inspector调试NodeJS代码 任何一门完备的语言技术栈都少不了健壮的调试工具,对于NodeJS平台同样如此,笔者研究了几种调试NodeJS代码的方式,通过对比,还是觉得node ...