tomcat accesslog日志扩展
由于工作需要,最近对tomcat的日志进行了一些研究,发现其日志大致可以分为两类,一类是运行日志,即平常我们所说的catalina.out日志,由tomcat内部代码调用logger打印出来的;另一类是accesslog访问日志,即记录外部请求访问的信息。处理这两类日志,tomcat默认采用了不同的方式,运行类日志默认采用的是java.util.logging框架,由conf下的logging.properties负责配置管理,也可以支持切换到log4j2(具体可参看我的前一篇博文:升级tomcat7的运行日志框架到log4j2 );对于访问日志,tomcat默认是按日期直接写进文件,由server.xml中配置Valve来管理。
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" pattern="%h %l %u %t "%r" %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 )
@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中。
- 创建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);
}
}
} - 处理可配的参数
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(); - 根据不同的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();
}
};
} - 在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方法。
tomcat accesslog日志扩展的更多相关文章
- 配置spring boot 内置tomcat的accessLog日志
#配置内置tomcat的访问日志server.tomcat.accesslog.buffered=trueserver.tomcat.accesslog.directory=/home/hygw/lo ...
- Tomcat AccessLog 格式化
有的时候我们要使用日志分析工具对日志进行分析,需要对日志进行格式化,比如,要把accessLog格式化成这样的格式 c-ip s-ip x-InstancePort date time-taken x ...
- spring boot Tomcat访问日志
1.Tomcat设置访问日志 <Host name="localhost" appBase="webapps" unpackWARs="true ...
- Tomcat中日志组件
Tomcat日志组件 AccessLog接口 public interface AccessLog { public void log(Request request, Response respon ...
- Tomcat 修改日志输出配置 定期删除日志
tomcat的下的日志catalina.out 和 qc.log疯狂增长,以下是解决办法 我生产环境tomcat版本 Server version: Apache Tomcat/7.0.35 Serv ...
- Tomcat各种日志的关系与catalina.out文件的分割
Tomcat 各日志之间的关系 一图胜千言! 其他日志如localhost.{yyyy-MM-dd}.log.localhost-access.{yyyy-MM-dd}.log是context的名称, ...
- shell脚本切割tomcat的日志文件
鉴于在调试logback和log4j的文件切割一直无法成功,随性用shell写个脚本用来切割tomcat下的日志文件(大家如果有在logback或log4j使用文件切割成功的话,可以留下使用方式,先谢 ...
- Tomcat访问日志详细配置
在server.xml里的<host>标签下加上 <Valve className="org.apache.catalina.valves.AccessLogValve&q ...
- tomcat详细日志配置
在server.xml里的<host>标签下加上<Valve className="org.apache.catalina.valves.AccessLogValve&qu ...
随机推荐
- 【Win10应用开发】协议-下篇:自定义多个协议
前面介绍了如何为应用程序自定义协议,于是有朋友会问,我希望为我的应用注册多个协议,不同的协议处理不同的事情,能吗?答案是能的. 方法主要在配置清单文件上,这里我给出一个例子,示例应用将注册两个协议,分 ...
- JavaScript Timer实现动画效果
<style type="text/css"> div { width: 100px; height: 50px; background: red; margin: 1 ...
- 启动第一个 KVM 虚机 - 每天5分钟玩转 OpenStack(4)
本节演示如何使用 virt-manager 启动 KVM 虚机. 首先通过命令 virt-manager 启动图形界面 # virt-manager 点上面的图标创建虚机 给虚机命名为 kvm1,这里 ...
- android模仿58筛选下拉框(PopupWindow实现)
前言:前几天用58同城APP找房子的时候,看到筛选下拉框蛮不错的,然后也有很多朋友需要实现这个功能,于是从网上下载了一个demo,在他的基础上进行修改,花了几个小时对他的代码进行修改,重构,封装.把一 ...
- 简单测试nginx1.90做TCP协议负载均衡的功能
最近工作中需要做TCP层面的负载均衡,以前网站用的反向代理nginx只支持应用层的负载均衡,对于TCP协议是无能为力的,需要使用LVS(linux虚拟服务器). LVS的特点是高性能和极复杂的配置.对 ...
- iOS开发之多线程技术(NSThread、OperationQueue、GCD)
在前面的博客中如果用到了异步请求的话,也是用到的第三方的东西,没有正儿八经的用过iOS中多线程的东西.其实多线程的东西还是蛮重要的,如果对于之前学过操作系统的小伙伴来说,理解多线程的东西还是比较容易的 ...
- OracleDBA之数据库管理
以下这些东西是我的麦库上存的当时学Oracle的学习笔记今天拿出来和大家分享一下,转载请注明出处,下面用的Oracle的版本是10g,用的时WinServer2003的操作系统,可能有些命令和Orac ...
- Spring学习笔记--环境搭建和初步理解IOC
Spring框架是一个轻量级的框架,不依赖容器就能够运行,像重量级的框架EJB框架就必须运行在JBoss等支持EJB的容器中,核心思想是IOC,AOP,Spring能够协同Struts,hiberna ...
- Effective java笔记(七),通用程序设计
45.将局部变量的作用域最小化 将局部变量的作用域最小化,可以增强代码的可读性和可维护性,并降低出错的可能性. Java允许在任何可以出现语句的地方声明变量(C语言中局部变量要在代码块开头声明),要使 ...
- MySQL如何利用索引优化ORDER BY排序语句
MySQL索引通常是被用于提高WHERE条件的数据行匹配或者执行联结操作时匹配其它表的数据行的搜索速度. MySQL也能利用索引来快速地执行ORDER BY和GROUP BY语句的排序和分组操作. 通 ...