1.环境准备
操作系统:CentOS7(虚拟机上跑)
Java版本:JDK1.7(只需要JVM,可以只安装jre)
ELK:ElasticSearch: 2.1.0,
Logstash: 2.1.1,
Kibana: 4.3.0 参考:
https://www.kancloud.cn/hanxt/elk/216151,
https://my.oschina.net/itblog/blog/547250
2.安装ElasticSearch
elasticsearch中文教程:http://elasticsearch.cn/book/elasticsearch_definitive_guide_2.x/《elasticsearch权威指南》
去官网下载压缩包,解压在CentOS7的 /home/lin/elk/elasticsearch-2.1.0/ 下
bin:可执行文件 config:配置文件
data:es放数据的文件夹
lib:运行时库
logs:日志文件
plugins:插件文件
进入到 elasticsearch 的解压目录下,安装head插件:
./bin/plugin install mobz/elasticsearch-head
如果权限不够要赋予权限。安装完head插件后进入plugins目录下看是否有一个head目录:ls plugins/
然后编辑 elasticsearch 的配置文件:
vi config/elasticsearch.yml
只需要修改下面几个参数:
cluster.name: es_cluster node.name: node0
#如果没有这个目录要新建一个 path.data: /home/lin/elk/elasticsearch-2.1.0/data
#如果没有这个目录要新建一个 path.logs: /home/lin/elk/elasticsearch-2.1.0/logs #当前hostname或IP,这里是虚拟机的IP地址 network.host=192.168.112.128
#接受http请求的端口 network.port=9200
保存修改的配置文件退出后运行elasticsearch:
./bin/elasticsearch
注意,在linux系统下不能以root用户权限运行elasticsearch,并且jdk版本至少要1.7,否则报错。
成功运行后如下:
其中9300是elasticsearch与其他节点传输的端口,9200是elasticsearch接受http请求的端口。
然后在外部机器上的浏览器访问:http://192.168.112.128:9200/ 在浏览器看到如下json串信息:
或者也可以使用curl命令 curl 'http://192.168.112.128:9200/?pretty' 来看是否成功启动,成功启动终端会打印一个json:
{
"name" : "node0",
"cluster_name" : "es_cluster",
"version" : {
"number" : "2.1.0",
"build_hash" : "72cd1f1a3eee09505e036106146dc1949dc5dc87",
"build_timestamp" : "2015-11-18T22:40:03Z",
"build_snapshot" : false,
"lucene_version" : "5.3.1"
},
"tagline" : "You Know, for Search"
}
如果无法访问,很可能需要检查虚拟机是否设置了防火墙,把9200端口开启一下:
/sbin/iptables -I INPUT -p tcp --dport 9200 -j ACCEPT
再访问。
上面安装的head插件是浏览器与elasticsearch交互的插件,用来在浏览器上查看集群状态,集群doc内容,执行搜索和普通的rest请求。在浏览器访问:http://192.168.112.128:9200/_plugin/head/看到如下界面:
至此 elasticsearch 基本成功搭建。
3.安装Logstash
Logstash的作用是用来收集日志并把日志输出到elasticsearch。
下载Logstash压缩包并解压到 /home/lin/elk/logstash-2.1.1目录,解压后并创建一个config目录后内容如下:
在config目录下新建一个配置文件:
vi config/log4j_to_es.conf
内容如下:
# For detail structure of this file # Set: https://www.elastic.co/guide/en/logstash/current/configuration-file-structure.html input { # For detail config for log4j as input, # See: https://www.elastic.co/guide/en/logstash/current/plugins-inputs-log4j.html
log4j {
mode => "server"
host => "192.163.112.128" #Logstash服务器的IP地址
port => 4567 #对外端口号,比如log4j配置日志输出到Logstash服务器的端口
}
}
filter {
#Only matched data are send to output.
} output {
# For detail config for elasticsearch as output, # See: https://www.elastic.co/guide/en/logstash/current/plugins-outputs-elasticsearch.html elasticsearch {
action => "index" #The operation on ES hosts => "192.168.112.128:9200" #ElasticSearch服务器的地址与端口,可以是一个数组,集群的时候. index => "applog" #The index to write data to.
}
}
保存文件,运行Logstash:
./bin/logstash agent -f config/log4j_to_es.conf
启动Logstash后看到下面内容:
Logstash startup completed
至此Logstash搭建基本完成。
4.接入Java项目
首先项目肯定需要log4j依赖:
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
classpath下创建log4j.properties文件,内容如下:
log4j.rootLogger=INFO,console
# for package com.demo.elk, log would be sent to socket appender.
#demo是对应输出日志的包的包名
log4j.logger.demo=DEBUG, socket
# appender socket
log4j.appender.socket=org.apache.log4j.net.SocketAppender
#logstash配置文件 log4j_to_es.conf配置的端口号
log4j.appender.socket.Port=4567
#logstash服务器IP地址
log4j.appender.socket.RemoteHost=192.168.112.128
log4j.appender.socket.layout=org.apache.log4j.PatternLayout
log4j.appender.socket.layout.ConversionPattern=%d [%-5p] [%l] %m%n
log4j.appender.socket.ReconnectionDelay=10000
# appender console
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.target=System.out
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d [%-5p] [%l] %m%n
Java项目结构:
代码输出日志:
package demo;
import org.apache.log4j.Logger;
public class LogstashDemo {
private static final Logger LOGGER = Logger.getLogger(LogstashDemo.class);
public static void main(String[] args) {
try {
for(int i=0;i<10;i++) {
LOGGER.error("Log content to logstash" + i);
Thread.sleep(500);
}
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
}
}
运行程序打印日志,然后浏览器进入 http://192.168.112.128:9200/_plugin/head/
点击一个doc,其中message是日志的源内容。
5.Kibana
配置Kibana:
tar -zxvf kibana-4.3.0-linux-x86.tar.gz cd kibana-4.3.0-linux-x86 vi config/kibana.yml
在kibana.yml文件中,编辑如下内容:
server.port: 5601 server.host: “192.168.128.112” elasticsearch.url: http://192.168.128.112:9200 kibana.index: “.kibana”
其中server.port是Kibana对外的端口,server.host是Kibana部署服务器的IP,elasticsearch.url是elasticsearch服务器的IP和端口。
启动Kibana:
进入Kibana的目录,运行 ./bin/kibana
然后在浏览器访问: http://192.168.112.128:5601/
6. Java项目上报日志到ELK
通过log4j配置一个socket appender作为项目输入日志到logstash的方式之外,还有一种更加常用的方式,就是所有项目引入一个统一的Appender,这个appender不影响日志的正常打印,并且它的内部维护一个队列和定时任务调度池,项目在配置了这个appender后将日志提交到这个队列中,调度池中的工作线程再把日志序列化成json插入到redis队列,logstash再配置一个redis作为输入的配置文件,从redis中取读取这些日志再输出到elasticsearch分析。
6.1 RedisAppender
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* Created by lin on 17-5-16.
*/
public class RedisAppender extends ConsoleAppender {
private String logCacheKey;
private int maxPerSize;
private int coreSize;
private int timeoutMilliSeconds;
private int initDelay;
private int period;
private ScheduledExecutorService scheduledExecutorService;
private Queue<LoggingEvent> logQueue = new LinkedBlockingQueue<>();
public RedisAppender() {
//安全发布
synchronized (this) {
if(scheduledExecutorService == null) {
scheduledExecutorService = Executors.newScheduledThreadPool(coreSize);
}
}
}
@Override
public void activateOptions() {
super.activateOptions();
scheduledExecutorService.scheduleAtFixedRate(new LogWorker(maxPerSize, timeoutMilliSeconds, logCacheKey),
initDelay, period, TimeUnit.MILLISECONDS);
}
@Override
public void append(LoggingEvent event) {
super.append(event);
if(logQueue.size() >= maxPerSize) {
LogLog.warn("log queue size is over max, customer will not push log to the queue");
} else {
if(event != null) {
LogLog.warn("offer logging event to log queue");
logQueue.offer(event);
}
}
}
class LogWorker implements Runnable {
private int maxPerSize;
private int timeoutMilliSeconds;
private String logCacheKey;
private List<LoggingEvent> tempList = new ArrayList<>();
private JSONEventLayoutV1 jsonEventLayoutV1 = new JSONEventLayoutV1();
public LogWorker(int maxPerSize, int timeoutMilliSeconds, String logCacheKey) {
this.maxPerSize = maxPerSize;
this.timeoutMilliSeconds = timeoutMilliSeconds;
this.logCacheKey = logCacheKey;
}
@Override
public void run() {
int idx = 0;
while (!logQueue.isEmpty() || idx < maxPerSize) {//这里取到一定的量时退出,防止日志量太大时取不完
LoggingEvent event = logQueue.poll();
if(event != null) {
tempList.add(event);
}
idx ++;
}
if(!tempList.isEmpty()) {
rpushBatch(tempList);
}
tempList.clear();
}
private void rpushBatch(List<LoggingEvent> list) {
if(list != null && !list.isEmpty()) {
for(LoggingEvent event : list) {
String logJson = jsonEventLayoutV1.format(event);
LogLog.warn("push log json string to redis queue:" + logJson);
JedisCache.rpush(logCacheKey, logJson, timeoutMilliSeconds);
}
}
}
}
public String getLogCacheKey() {
return logCacheKey;
}
public void setLogCacheKey(String logCacheKey) {
this.logCacheKey = logCacheKey;
}
public int getMaxPerSize() {
return maxPerSize;
}
public void setMaxPerSize(int maxPerSize) {
this.maxPerSize = maxPerSize;
}
public int getCoreSize() {
return coreSize;
}
public void setCoreSize(int coreSize) {
this.coreSize = coreSize;
}
public int getTimeoutMilliSeconds() {
return timeoutMilliSeconds;
}
public void setTimeoutMilliSeconds(int timeoutMilliSeconds) {
this.timeoutMilliSeconds = timeoutMilliSeconds;
}
public int getInitDelay() {
return initDelay;
}
public void setInitDelay(int initDelay) {
this.initDelay = initDelay;
}
public int getPeriod() {
return period;
}
public void setPeriod(int period) {
this.period = period;
}
}
6.2在log4j.properties配置这个appender
log4j.rootLogger = INFO, REDIS
log4j.appender.consoleAppender = org.apache.log4j.ConsoleAppender
log4j.appender.consoleAppender.layout = org.apache.log4j.PatternLayout
log4j.appender.consoleAppender.layout.ConversionPattern = %-4r [%t] %5p %c %x - %m - [%X{userName}]%n
log4j.appender.REDIS= com.test.RedisAppender
log4j.appender.REDIS.layout = org.apache.log4j.PatternLayout
log4j.appender.REDIS.layout.ConversionPattern = %-4r [%t] %5p %c %x - %m%n
log4j.appender.REDIS.logCacheKey = logstash-
log4j.appender.REDIS.maxPerSize = 100
log4j.appender.REDIS.coreSize = 2
log4j.appender.REDIS.timeoutMilliSeconds = 10000
log4j.appender.REDIS.initDelay = 3000
log4j.appender.REDIS.period = 1000
6.3 logstash配置redis作为输入
新加个配置文件 redis_to_es.conf:
input {
redis {
data_type => "list" #logstash redis插件工作方式
key => "mycompany-logstash-" #监听的键值
host => "192.168.112.128" #redis地址
port => 6379 #redis端口号
password => "123" #如果有安全认证,此项为密码
}
}
filter {
#Only matched data are send to output.
}
output {
elasticsearch {
action => "index" #The operation on ES
hosts => "192.168.112.128:9200" #ElasticSearch服务器的地址与端口,可以是一个数组,集群的时候.
index => "applog" #The index to write data to.
}
}
6.4 日志触发
import org.apache.log4j.Logger;
public class App {
private static Logger log = Logger.getLogger(App.class);
public static void main( String[] args ) {
for(int i=0;i<100;i++) {
log.info("log message to redis:" + i);
}
}
}
6.5 日志收集结果
去Kibana中看最新的日志: