Shell脚本学习(五)/dev/null的用法

在类似启动Tomcat的Shell脚本中我们经常会看到如:./startup.sh > /dev/null,正常这种用法都是执行./startup.sh启动脚本后,将log输出到控制台的。但是用./start.sh > /dev/null这种方式就类似daemon后台启动Tomcat的效果一样,但是启动的日志不会输出到log文件,也不会输出到控制台,而是输出到/dev/null。可以把/dev/null看作一个”黑洞”,它非常等价于一个只写文件,所有写入它的内容都会永远丢失,而尝试从它那儿读取内容则什么也读不到。所以我们是在任何地方都看不到这个Tomcat的启动log的。

1
2
3
4
5
6
7
8
9
10
11
12
13
1. 禁止标准输出
# 将catalina.out日志文件的内容输出到控制台
cat catalina.out
# 将catalina.out日志文件的内容输出到test.log文件中
cat catalina.out > test.log
# 将catalina.out日志文件的内容输出到/dev/null(黑洞)
cat catalina.out > /dev/null
2. 禁止输出ls文件列表
# 输出当前文件夹下的所有文件列表到ls.out文件中
ls > ls.out
# 将文件列表实际是输出到/dev/null(黑洞)
ls > /dev/null

这里在说明一下 < , > , >> , 2> 的区别

  • 由 < 的右边读入参数档案
  • 将原本由屏幕输出的正确数据输出到 > 右边的 file ( 文件名称 ) 或 device ( 装置,如 printer )去
  • 将原本由屏幕输出的正确数据输出到 >> 右边,与 > 不同的是,该档案将不会被覆盖,而新的数据将以『增加的方式』增加到该档案的最后面
  • 将原本应该由屏幕输出的错误数据输出到 2> 的右边去
1
2
3
4
5
6
7
8
# 正确的ls命令
$ ls -al
# 将显示的数据,不论正确或错误均输出到success.log(注意:2>&1中间不能有空格)
$ ls -al 1> success.log 2>&1
# 将显示的数据,正确的输出到success.log,错误的输出则予以丢弃,这里故意将ls命令的参数丢掉,让命令出错!
$ ls - 1> success.log 2> /dev/null
# 将显示的数据,正确的输出到success.log,错误的输出到error.log,这里故意将ls命令的参数丢掉,让命令出错!
$ ls - 1> success.log 2> error.log

Kibana学习(二)图形报表的使用

Kibana已经给我们提供了多种不同的报表形式,如下图所示

Kibana_Visualization

图表名称 描述
Area chart 区域图
Data table 表格
Line chart 线性图
Markdown widget Markdown
Metric 统计计数
Pie chart 饼图
Tile map 地图分布
Vertical bar chart 柱状图
  • metrics(度量):具体考察的数量值,例如:数量,金额等等(这里就类似OLAP中的度量的概念),可以选择多个metrics度量来考察数量值。
  • buckets(维度):观察数据的角度,从多个角度作为标准衡量数据,例如:时间,地点,类型等等(这里就类似OLAP中的维度的概念),可以选择多个buckets维度来观察数据。

下面我们用以前做的command命令行日志收集来创建不同的Kibana图表

Area chart区域图,Line chart线性图,Vertical bar chart柱状图

因为Area chart,Line chart,Vertical bar chart是X-Y轴的图结构,所以一般情况是Y轴是统计的数据的值(也就是度量),X轴可以选择我们的索引字段作为观察数据的角度(也就是维度)

metrics

1
2
3
4
5
6
7
8
9
# Y-Axis:设置Y轴的度量是Count
{
Aggregation: Count
}
# Y-Axis:设置Y轴的度量是Unique Count
{
Aggregation: Unique Count
}

buckets

1
2
3
4
5
6
# X-Axis:设置X轴的维度是@timestamp字段
{
Aggregation: Date Histogram,
Field: @timestamp,
Interval: Daily
}
1
2
3
4
5
6
7
8
9
10
# Split Area:设置可以按照CMD字段在同一个Area chart中区分统计不同的Count值
# Split Line:设置可以按照CMD字段在同一个Line chart中区分统计不同的Count值
# Split Bars:设置可以按照CMD字段在同一个Bars chart中区分统计不同的Count值
{
Sub Aggregation: Terms,
Field: CMD,
Order: Top,
Size: 5,
Order By: metric:Count
}
1
2
3
4
5
6
7
8
# Split Chart:设置可以按照host字段拆分出多个chart统计不同的Count值
{
Sub Aggregation: Terms,
Field: host,
Order: Top,
Size: 5,
Order By: metric:Count
}

Pie chart饼状图

因为Pie chart是没有X-Y轴的图结构,所以一般情况是slice切片是统计的数据的值(也就是度量),Split Slices可以选择我们的索引字段用来切分数据(也就是维度)

metrics

1
2
3
4
# Slice Size:设置slice切片统计的度量是Count
{
Aggregation: Count
}

buckets

1
2
3
4
5
6
7
8
# Split Slices:设置可以按照CMD字段在同一个Pie chart中区分统计不同的Count值
{
Aggregation: Terms,
Field: host,
Order: Top,
Size: 5,
Order By: metric:Count
}
1
2
3
4
5
6
7
8
# Split Chart:设置可以按照host字段拆分出多个chart统计不同的Count值
{
Sub Aggregation: Terms,
Field: host,
Order: Top,
Size: 5,
Order By: metric:Count
}

参考文章:

Kibana学习(一)环境安装总结

之前在搭建ELK环境的过程中遇到了一些问题,在此进行总结,以后会不断更新的。

Kibana的默认索引.kibana无法在ES创建

这个问题是因为我在ES中安装了ik插件,修改了elasticsearch.yml的配置文件,但是没有在ES的lib包目录中引入ik的jar包,所以在Kibana访问ES的时候ES报错了,检查ES的log文件发现了是elasticsearch.yml的ik插件配置没有找到ik的jar包

  • 所以Kibana报错最好的找错方式就是看ES的log

ES创建了User的index之后,在Kibana配置该索引时’Time-field name’为空,没有@Timestamp选项

这个问题是因为ES创建的User的mapping中没有个一个字段是date类型,所以就没有@Timestamp字段,修改User的mapping重新索引数据后@Timestamp字段就出现了,如果需要手动添加Timestamp字段可以参考如下步骤。

1
2
3
1. Go to Settings → Advanced.
2. Edit the metaFields and add "_timestamp". Hit save.
3. Now go back to Settings → Indices and _timestamp will be available in the drop-down list for "Time-field name".

metaFields

参考文章:

Kibana创建图表的时候,选择的Aggregation Terms字段报错,该字段已经被Analyzed分词了,无法被Aggregation Terms使用

问题是因为我自己创建的索引command_index的mapping,所有的String字段都没有指定分词情况,所以ES默认都是Analyzed分词。所以这里只需要将mapping修改一下,将所有的String类型字段都指定分词方式(“index”:”not_analyzed”),这样指定的字段就不会分词了,就可以使用Aggregation Terms方式创建图表了。(注意:一定要删除原来的mapping,重新索引数据才可以)

Kibana使用Pie Charts错误:”Pie chart response converter:Splitting charts without splitting sliced is not supported.Pretend that we are just splitting slices”

其实这个错误是因为在创建图表的时候选择了错误的类型,本来只是想创建一个单个的饼图(用线来切分的),所以应该使用Split Slice类型(从字面上应该也能理解),但是却选择了Split Chart类型了,所以就会出现上面的错误。

下面是来自stackoverflow的解释,还清晰的给出了这两个类型的例子,很直观的区别开了。

1
If you want to do a "split slice " operation on a pie chart,first you need to create a pie chart with slices.Here from what I understand,you tried to give the option "split chart" first,which actually is to make differrent pie charts,in the same row or column,which needs more than one pie-chart. This also requires pie-charts (with slices) to be created first. So you need to create pie-charts and then only you can use "split chart". i In the figures below,the first one shows an ordinary pie-chart created by "split-slice" and the second one shows five pie charts stacked horizontally using the "split chart" method.

Split Slice
Split Chart

参考文章:

ELK实战 —— 收集浏览器的浏览历史记录(二)

这里我们只收集Safari,Chrome,FireFox三个主流浏览器

首先需要找到各个浏览器的历史记录所存储在本地操作系统的路径

所有浏览器的历史记录的存储都是用的sqlite数据库,是一个轻量级的客户端数据库,支持标准的sql查询,可以使用sqlite数据库客户端连接(我这里用的Navicat)。我用的Mac操作系统,所以我的sqlite数据库文件的地址如下:(不同的操作系统路径有所不同,请根据自身环境查找)

1
2
3
4
5
6
# Safari
/Users/ben/Library/Safari/History.db
# Chrome
/Users/ben/Library/Application Support/Google/Chrome/Default/History
# FireFox
/Users/ben/Library/Application Support/Firefox/Profiles/teofen8x.default/places.sqlite

Safari浏览器数据库

Safari浏览器数据库

Chrome浏览器数据库

Chrome浏览器数据库

FireFox浏览器数据库

FireFox浏览器数据库

打开sqlite数据库,遇到’database is locked’问题

原因是sqlite不支持并发执行写入操作,即使是不同的表,只支持库级锁。所以Navicat打开sqlite数据库文件时会提示’database is locked’。知道了sqlite报这个错误的原因了,解决起来也比较简单,只要关掉当前浏览器的所有进程,重新打开sqlite文件就不会报错了,因为没有浏览器进程访问sqlite数据库了。

Logstash解析sqlite遇到Cannot find Serializer for class: org.jruby.RubyObject错误

1
2
{:timestamp=>"2016-08-06T11:45:25.875000+0800", :message=>"Got error to send bulk of actions: Cannot find Serializer for class: org.jruby.RubyObject", :level=>:error}
{:timestamp=>"2016-08-06T11:45:25.875000+0800", :message=>"Failed to flush outgoing items", :outgoing_count=>80, :exception=>"JrJackson::ParseError", :backtrace=>["com/jrjackson/JrJacksonBase.java:78:in `generate'", "/Users/ben/dev/logstash-1.5.6/vendor/bundle/jruby/1.9/gems/jrjackson-0.3.7/lib/jrjackson/jrjackson.rb:59:in `dump'", "/Users/ben/dev/logstash-1.5.6/vendor/bundle/jruby/1.9/gems/multi_json-1.11.2/lib/multi_json/adapters/jr_jackson.rb:20:in `dump'", "/Users/ben/dev/logstash-1.5.6/vendor/bundle/jruby/1.9/gems/multi_json-1.11.2/lib/multi_json/adapter.rb:25:in `dump'", "/Users/ben/dev/logstash-1.5.6/vendor/bundle/jruby/1.9/gems/multi_json-1.11.2/lib/multi_json.rb:136:in `dump'", "/Users/ben/dev/logstash-1.5.6/vendor/bundle/jruby/1.9/gems/elasticsearch-api-1.0.15/lib/elasticsearch/api/utils.rb:102:in `__bulkify'", "org/jruby/RubyArray.java:2414:in `map'", "/Users/ben/dev/logstash-1.5.6/vendor/bundle/jruby/1.9/gems/elasticsearch-api-1.0.15/lib/elasticsearch/api/utils.rb:102:in `__bulkify'", "/Users/ben/dev/logstash-1.5.6/vendor/bundle/jruby/1.9/gems/elasticsearch-api-1.0.15/lib/elasticsearch/api/actions/bulk.rb:82:in `bulk'", "/Users/ben/dev/logstash-1.5.6/vendor/bundle/jruby/1.9/gems/logstash-output-elasticsearch-1.1.0-java/lib/logstash/outputs/elasticsearch/protocol.rb:105:in `bulk'", "/Users/ben/dev/logstash-1.5.6/vendor/bundle/jruby/1.9/gems/logstash-output-elasticsearch-1.1.0-java/lib/logstash/outputs/elasticsearch.rb:548:in `submit'", "/Users/ben/dev/logstash-1.5.6/vendor/bundle/jruby/1.9/gems/logstash-output-elasticsearch-1.1.0-java/lib/logstash/outputs/elasticsearch.rb:572:in `flush'", "/Users/ben/dev/logstash-1.5.6/vendor/bundle/jruby/1.9/gems/stud-0.0.21/lib/stud/buffer.rb:219:in `buffer_flush'", "org/jruby/RubyHash.java:1342:in `each'", "/Users/ben/dev/logstash-1.5.6/vendor/bundle/jruby/1.9/gems/stud-0.0.21/lib/stud/buffer.rb:216:in `buffer_flush'", "/Users/ben/dev/logstash-1.5.6/vendor/bundle/jruby/1.9/gems/stud-0.0.21/lib/stud/buffer.rb:193:in `buffer_flush'", "/Users/ben/dev/logstash-1.5.6/vendor/bundle/jruby/1.9/gems/stud-0.0.21/lib/stud/buffer.rb:112:in `buffer_initialize'", "org/jruby/RubyKernel.java:1479:in `loop'", "/Users/ben/dev/logstash-1.5.6/vendor/bundle/jruby/1.9/gems/stud-0.0.21/lib/stud/buffer.rb:110:in `buffer_initialize'"], :level=>:warn}

刚好在github的issue里面有提到过,后来自己看了一下#298这个issue,从中找到了问题的原因。问题的原因就是sqlite的数据中某个字段无法用JrJackson序列化,也就是数据有问题,后来我仔细观察了一下我自己的sqlite数据,果然有个db字段的数据类型有问题,我在logstash filter中把db这个字段remove了就不会报错了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"host" => "localhost",
"db" => #<Sequel::JDBC::Database: "jdbc:sqlite:/Users/ben/Library/Safari/History.db">,
"@version" => "1",
"@timestamp" => "2016-08-08T05:54:37.985Z",
"type" => "safari_history_visits",
"history_item" => 55723,
"visit_time" => 492328032.0,
"title" => "Cannot find Serializer for class: org.jruby.RubyObject · Issue #298 · logstash-plugins/logstash-output-elasticsearch",
"load_successful" => true,
"http_non_get" => false,
"synthesized" => false,
"redirect_source" => nil,
"redirect_destination" => nil,
"origin" => 0,
"generation" => 31154
}

参考文章:

不同浏览器的timestamp时间格式问题

每个浏览器的timestamp时间戳字段用的格式都不一样,真的很头疼啊

Safari Timestamp

Safari的浏览时间是history_visit表中的visit_time字段,”visit_time” => 492328032.0,根据这个时间戳转换出来的实际时间是’1985/8/8 13:47:12’(浏览时间是1985年老子还没出生呢,根本不可能啊),后来又google了一下,发现Safari用的NSDate,是IOS的一个日期类型,这个时间戳是从2001-01-01 00:00:00开始的,所以要加上492328032.0 (visit_time) + 978307200 = 1470635232转换出来的实际时间’2016-08-08 13:47:12’才正确。

1
2
# NSDate Class Reference
NSDate objects encapsulate a single point in time, independent of any particular calendrical system or time zone. Date objects are immutable, representing an invariant time interval relative to an absolute reference date (00:00:00 UTC on 1 January 2001).
FireFox Timestamp

FireFox的浏览时间是moz_historyvisits表中的visit_date字段,”visit_date” => 1470469974041830,但是这个时间戳既不是10位的也不是13位的,所以截取前13位就能转换成正确的实际时间’2016-08-06 15:52:54’

Chrome Timestamp

在visit_time和last_visit_time字段下,我的是13115139002559995。这么长的一串字肯定是微秒级的(真TM长啊。。),按照Unix时间戳截断10位后1311513900,转换成实际的时间是’2011/7/24 21:25:0’这也不对啊,最后一次使用Chrome浏览器浏览网页明明是昨天啊。。我又郁闷了。。还是找了google老大帮忙,发现Chrome的浏览记录visit_time和last_visit_time的起始值是1601年1月1日0时0分0秒,和Safari一样坑。。所以要减去11644473600 (visit_time) - 11644473600 = 1470665402转换出来的实际时间’2016-08-08 22:10:02’才正确

1
2
3
"[Google Chrome's] timestamp is formatted as the number of microseconds since January, 1601"
> SELECT datetime((visit_time/1000000)-11644473600, 'unixepoch', 'localtime') AS time FROM visits ORDER BY visit_time DESC LIMIT 10;

参考文章:

Logstash启动后没有debug日志输出,Logstash进程一直是sleeping状态

Logstash进程状态
后来发现是因为Logstash的filter插件配置有问题,导致没有匹配到相应的日志,所以进程一直处于sleeping等待状态

无法同步全量的浏览到ES服务器

启动Logstash同步一次sqlite数据库之后,发现以后再重启Logstash服务也无法重新再读取全部的sqlite数据,后来看了一下logstash-input-sqlite插件的源码,发现logstash-input-sqlite插件会创建一个表since_table用来记录读取sqlite数据库的表和对应表的last_id,这样即使重启Logstash服务,logstash-input-sqlite插件也会从since_table表中记录的最后一次读取的id开始读取,所以如果想要全量重新同步sqlite中的数据就把since_table这个表删了

logstash无法处理时间戳转换问题

(未完待续)
logstash的date,mutate,grok插件都不支持上面所描述的浏览器时间戳做加减操作,所以只能扩展logstash-input-sqlite插件,在sqlite插件中做sql查询来处理,但是查看了logstash-input-sqlite插件的源码后,发现logstash-input-sqlite插件都是用的select *方式做的表查询,在logstash的配置中也无法指定相应的字段,所以需要对logstash-input-sqlite插件本身进行扩展。

grok使用自定义正则表达式

Safari和Chrome的时间戳转换的问题可能在Logstash是无法操作的,但是FireFox的时间戳只是需要做简单的截取就可以转换成实际的时间,这样我们只需要自己写一个grok正则表达式来匹配上FireFox的时间戳就可以了。

自定义的grok正则表达式我们是存储在${LOGSTASH_HOME}/patterns/postfix文件中的,这里我们是截取了FireFox时间戳的前13位

${LOGSTASH_HOME}/patterns/postfix

1
FIREFOX_TIME [0-9]{13}

logstash_firefox.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
...
filter {
grok {
# 指定自定义grok正则表达式文件的路径
patterns_dir => "./patterns"
# 使用了自定义的FIREFOX_TIME表达式去匹配last_visit_date字段
match => {
"last_visit_date" => "%{FIREFOX_TIME:last_visit_date}"
}
overwrite => [ "last_visit_date" ]
}
mutate {
remove_field => [ "db" ]
}
date {
match => ["last_visit_date", "UNIX_MS"]
target => "@timestamp"
}
}
...

logstash-input-sqlite插件不支持sqlite做多表关联查询操作

(未完待续)
还有就是这三个浏览器的url地址和visitTime浏览时间是分开存储的,url地址都是放在统计表中的,而visitHistory浏览历史只记录url的id和浏览时间,所以要想提取原始的visit浏览数据需要做sqlite表关联查询,所以这里也只能扩展logstash-input-sqlite插件的功能。

1
2
3
FireFox : moz_places, moz_historyvisits
Chrome : urls, visits
Safari : history_items, history_visits

(未完待续)

AWK学习(一)

Nginx/Apache/Lnmp网站常用日记统计命令
发布时间:April 13, 2012 // 分类:日记分析 // No Comments
Nginx需配置日记格式为Apache日志格式,便于分析。
1.访问次数最多的前10个IP。

1
awk '{print $1}' www.haiyun.me.log|sort|uniq -c|sort -rn|head -n 10

2.访问次数最多的10个页面。

1
awk '{print $7}' www.haiyun.me.log|sort|uniq -c|sort -rn|head -n 10

3.访问最多的时间,取前十个。

1
awk '{print $4}' www.haiyun.me.log|cut -c 14-18|sort|uniq -c|sort -rn|head -n10

4.查看下载次数最多的文件,显示前10个。

1
2
awk '{print $7}' www.haiyun.me.log|awk -F '/' '{print $NF}'|sort|uniq -c|sort -rn|head -n 10
#如统计请求链接去除awk -F '/' '{print $NF}'|sort|

5.统计网站流量,以M为单位。

1
awk '{sum+=$10} END {print sum/1024/1024}' www.haiyun.me.log

6.统计IP平均流量、总流量。

1
2
awk 'BEGIN {print"ip average total"}{a[$1]+=$10;b[$1]++}END{for(i in a)print i,a[i]/1024/1024/b[i]"MB",\
a[i]/1024/1024"MB"}' www.haiyun.me.log |column -t

7.用sed统计特定时间内日志,配合以上使用awk分析。

1
2
3
4
5
sed -n '/10\/Feb\/2012:18:[0-9][0-9]:[0-9][0-9]/,$p' www.haiyun.me.log
#截取二月10号18点后所有日志
sed -n '/10\/Feb\/2012:18:[0-9][0-9]:[0-9][0-9]/,/10\/Feb\/2012:20:[0-9][0-9]:[0-9][0-9]/p' \
www.haiyun.me.log
#截取二月10号18点到20点之间日志

8.统计404或403最多的网址。

1
2
awk '$9 ~ /403/ {print $7}' www.haiyun.me.log|sort|uniq -c|sort -rn|head -n 80
awk '$9 ~ /404/ {print $7}' www.haiyun.me.log|sort|uniq -c|sort -rn|head -n 80

参考文章:

Shell脚本学习(四)EOF的用法

Shell脚本的EOF

EOF通常与<<结合使用,<<EOF表示后续的输入作为子命令或子shell的输入参数,直到遇到EOF,再次返回到主shell中。EOF可将其理解为分界符(delimiter),它的作用就是将两个 delimiter 之间的内容传递给交互式程序(cmd命令)作为输入参数。

其使用形式如下:

1
2
3
4
5
交互式程序(cmd命令)<<EOF
command1
command2
...
EOF
  • 注意,最后的EOF必须单独占一行。

EOF一般常和cat命令连用。一般有以下两种形式

1
2
3
4
5
1.将内容直接输出到标准输出(屏幕)
cat <<EOF
2.将标准输出进行重定向,将本应输出到屏幕的内容重定向到文件
cat <<EOF > filename或者cat <<EOF >> filename
1
2
3
4
5
6
7
# 将下面的脚本内容输出到helloworld.sh文件中
$ cat << EOF > helloworld.sh
> echo "hello"
> echo "world"
> EOF
$ sh helloworld.sh

ELK实战 —— 收集浏览器的浏览历史记录(一)

这里我们只收集Safari,Chrome,FireFox三个主流浏览器

这三个主流的浏览器都是使用的sqlite数据库来存储浏览历史记录的,所以我们要想收集sqlite的数据,需要先安装logstash-input-sqlite插件

插件的GitHub地址:

sqlite插件的github上给出了两种安装方式

  • 方式一:修改Gemfile文件方式安装(推荐使用)
  • 方式二:自行使用gem命令build插件

安装步骤

1
2
3
4
5
6
7
8
9
10
11
# 编辑Gemfile文件,在文件的最后面添加下面的配置
# path:可以下载插件到本地通过path指定本地路径安装,如果自己本地有修改sqlite插件需要指定本地插件的路径
gem "logstash-filter-awesome", :path => "/your/local/logstash-filter-awesome"
# 这里我们使用默认安装,不需要指定path
gem "logstash-input-sqlite"
# 安装插件
# Logstash 2.3以上的版本使用
bin/logstash-plugin install --no-verify
# Logstash 2.3之前的版本使用
bin/plugin install --no-verify

无法安装插件

如果无法安装logstash的插件,很有可能是logstash插件数据源被墙了,所以Gemfile配置文件最好使用国内的数据源配置,一般都能安装成功的。

修改Gemfile文件的gem数据源地址

1
2
# source "https://rubygems.org"
source "https://ruby.taobao.org/"

本地安装插件

有的插件在淘宝的gem库中找不到,这时候可以考虑本地安装的办法。

先去https://github.com/logstash-plugins下载对应的插件,然后解压,在logstash的Gemfile中添加一行(以logstash-output-webhdfs为例):

1
2
3
4
# 编辑Gemfile文件,在文件的最后面添加下面的配置
gem "logstash-output-webhdfs", :path => "/home/ec2-user/logstash-output-webhdfs"
bin/plugin install --no-verify

参考文章:

ELK实战 —— 收集Command命令执行的历史记录(一)

先说明一下我的安装环境,我用的是MacBook所以环境可能和Linux系统稍有不同,这里使用ELK来收集Command命令的执行历史记录,在架构上做了简化直接使用Logstash收集log文件,然后output输出到ES,所以Logstash没有区分Shipper和Indexer,也没有使用Redis做缓冲处理,后续会再细化补充。

首先将Command命令保存到log文件中

在/etc/bashrc里添加下面的内容来保存Command命令执行的历史记录到log文件中

1
2
3
4
5
6
7
8
9
10
11
# HISTDIR是Command命令保存的log文件路径,这里需要注意当前操作用户一定要对该log文件路径有读写权限
HISTDIR='/Users/ben/Downloads/command.log'
if [ ! -f $HISTDIR ];
then touch $HISTDIR sudo chmod 666 $HISTDIR
fi
# 定义Command日志的格式
export HISTTIMEFORMAT="{\"TIME\":\"%F %T\",\"HOSTNAME\":\"$HOSTNAME\",\"LI\":\"$(who -u am i 2>/dev/null| awk '{print $NF}'|sed -e 's/[()]//g')\",\"LU\":\"$(who am i|awk '{print $1}')\",\"NU\":\"${USER}\",\"CMD\":\""
# 输出日志到指定的log文件
export PROMPT_COMMAND='history 1|tail -1|sed "s/^[ ]\+[0-9]\+ //"|sed "s/$/\"}/">> /Users/ben/Downloads/command.log'

Logstash配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
input {
file {
path => ["/Users/ben/Downloads/command.log"]
type => "command"
codec => "json"
start_position => "beginning"
}
}
output {
stdout {
codec=>rubydebug
}
elasticsearch {
embedded => false
codec => "json"
host => "127.0.0.1"
port => 9200
protocol => "http"
index => "command_index"
}
}

ES的mapping需要注意的地方

因为我自己创建的索引command_index的mapping,所有的String字段都没有指定分词情况,所以ES默认都是Analyzed分词。所以这里只需要将mapping修改一下,将所有的String类型字段都指定分词方式(“index”:”not_analyzed”),这样指定的字段就不会分词了,就可以使用Aggregation Terms方式创建图表了。(注意:一定要删除原来的mapping,重新索引数据才可以)

重新生成ES索引数据,Logstash配置需要注意的地方start_position和sincedb

因为我们没有做持久化存储,所以当索引数据有问题的时候,我们只能重新从log文件中读取所有日志信息重新生成ES索引

1
2
3
4
5
6
# start_position是监听的位置,默认是end,即一个文件如果没有记录它的读取信息,则从文件的末尾开始读取,也就是说,仅仅读取新添加的内容。对于一些更新的日志类型的监听,通常直接使用end就可以了;相反,beginning就会从一个文件的头开始读取。但是如果记录过文件的读取信息,这个配置也就失去作用了。
start_position => "beginning"
# sincedb文件使用来保存logstash读取日志文件的进度的
# 默认存储在home路径下.sincedb_c9a33fda01005ad7430b6ef4a0d51f8b,可以设置sincedb_path指定该文件的路径
# c9a33fda01005ad7430b6ef4a0d51f8b是log文件路径"/Users/ben/Downloads/command.log"做MD5后的值

重新创建索引,@Timestamp的时间是重新创建索引的时间,不是command执行的时间

这里重新创建索引@Timestamp字段是我们重新创建索引的时间,而不是command命令执行的时间,这里我们的日志中是有一个TIME字段是command执行的实际时间的,所以需要在logstash中将TIME字段转成@Timestamp字段,logstash的配置修改如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
input {
file {
path => ["/Users/ben/Downloads/command.log"]
type => "command"
codec => "json"
start_position => "beginning"
}
}
filter {
date {
# 将TIME字段的时间输出到@timestamp字段
match => ["TIME", "yyyy-MM-dd HH:mm:ss"]
target => "@timestamp"
}
}
output {
stdout {
codec=>rubydebug
}
elasticsearch {
embedded => false
codec => "json"
host => "127.0.0.1"
port => 9200
protocol => "http"
index => "command_index"
}
}

Kibana效果图

Kibana_Dashboard

参考文章:

ElasticSearch环境搭建(2.x版本)

基本步骤如下(ES版本是2.3.x)

  1. 安装Java环境
  2. 安装ES
  3. 安装head,bigdesk插件
  4. 安装ik分词插件
  5. 安装jdbc-river插件(后续补上)
  6. 其他插件的安装(后续补上)

安装Java环境

1
2
3
4
5
6
7
8
9
10
11
12
# 下载安装JDK8,具体JDK的下载地址不提供了
$ tar -zxvf jdk-8u91-linux-x64.tar.gz
$ mkdir /usr/local/java/
$ cp -r jdk1.8.0_91 /usr/local/java/
# 配置Java环境变量
$ vi /etc/profile
# 添加Java环境变量
export JAVA_HOME=/usr/local/java/jdk1.8.0_91
export PATH=$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATH
export CLASSPATH=$CLASSPATH:.:$JAVA_HOME/lib:$JAVA_HOME/jre/lib

安装ES环境

1
2
3
4
5
6
7
8
9
10
11
12
# 下载安装ES 2.3.5
$ wget https://download.elastic.co/elasticsearch/elasticsearch/elasticsearch-2.3.5.tar.gz
$ tar -zxvf elasticsearch-2.3.5.tar.gz
$ mkdir /opt/
$ cp -r elasticsearch-2.3.5 /opt/
# 启动ES
$ cd /opt/elasticsearch-2.3.5/bin
$ ./elasticsearch
# 如果启动的时候报错,是没有权限创建log文件,尝试给ES_HOME目录的权限授予给当前用户
$ chown -R ben:root /opt/elasticsearch-2.3.5

安装ES插件head

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 两种安装方式(在线安装和离线安装)
# 注意:2.x和1.x版本的安装可能稍有不同,具体参考head的github
# 在线安装head插件
$ cd /opt/elasticsearch-2.3.5/bin
$ ./plugin install mobz/elasticsearch-head
# 离线安装head插件
# 从github下载head插件的master.zip
$ cd /opt/elasticsearch-2.3.5/bin
$ ./plugin --install head --url file:///home/ben/Downloads/elasticsearch-head-master.zip
# 具体参考github地址:
https://github.com/mobz/elasticsearch-head

安装ik插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 手动安装ik插件和1.x版本没什么区别,从github下载对应的ik插件版本,然后maven手动编译打包
$ git clone https://github.com/medcl/elasticsearch-analysis-ik
$ cd elasticsearch-analysis-ik
$ mvn clean
$ mvn compile
$ mvn package
# 拷贝和解压release下的文件: #{project_path}/elasticsearch-analysis-ik-*/target/releases/elasticsearch-analysis-ik-*.zip 到你的 elasticsearch 插件目录, 如: plugins/ik 重启elasticsearch
# 这里2.x版本直接把所有的配置都放在zip包里了,1.x版本还需要自己手动复制config配置文件夹,现在直接解压zip包到${ES_HOME}/plugins/ik/目录即可
$ cd #{project_path}/elasticsearch-analysis-ik-*/target/releases
$ unzip -o elasticsearch-analysis-ik-*.zip -d ${ES_HOME}/plugins/ik/
# 注意:2.x的版本安装ik插件不需要修改elasticsearch.yml配置文件了
# 具体参考github地址:
https://github.com/medcl/elasticsearch-analysis-ik

如果报错如下,是因为没有将elasticsearch-analysis-ik-*.zip的内容解压到${ES_HOME}/plugins/ik/目录

1
2
Exception in thread "main" java.lang.IllegalStateException: Could not load plugin descriptor for existing plugin [analysis-ik]. Was the plugin built before 2.0?
Likely root cause: java.nio.file.NoSuchFileException: /home/es/es2/plugins/analysis-ik/plugin-descriptor.properties

访问地址

如果需要通过ip进行访问es集群,必须修改elasticsearch.yml中的network.host节点。es 1.x版本的默认配置是”0.0.0.0”,所以不绑定ip也可访问,但是es 2.x版本如果采用默认配置,只能通过 localhost 和 “127.0.0.1”进行访问,所以这里需要修改network.host配置成内网IP或者外网IP。network.host也可以配置多个值。

1
2
# 该IP根据自己环境的实际情况修改
network.host: 10.23.41.252

测试ik分词

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# User的mapping
{
"mappings": {
"user": {
"dynamic" : "strict",
"properties": {
"id": {
"type": "string",
"index": "not_analyzed"
},
"name": {
"type": "string",
"analyzer": "ik",
"search_analyzer": "ik_smart"
},
"age": {
"type": "integer"
},
"job": {
"type": "string",
"analyzer": "ik",
"search_analyzer": "ik_smart"
},
"createTime": {
"type": "long"
}
}
}
}
}
# 尝试创建user索引
$ curl -XPOST 'http://127.0.0.1:9200/user?pretty' -d '{
"mappings": {
"user": {
"dynamic" : "strict",
"properties": {
"id": {
"type": "string",
"index": "not_analyzed"
},
"name": {
"type": "string",
"analyzer": "ik",
"search_analyzer": "ik_smart"
},
"age": {
"type": "integer"
},
"job": {
"type": "string",
"analyzer": "ik",
"search_analyzer": "ik_smart"
},
"createTime": {
"type": "long"
}
}
}
}
}'
# 创建索引成功后,查看索引信息
$ curl -XGET 'http://127.0.0.1:9200/_cat/indices?pretty'
green open user 5 1 0 0 970b 575b
# 测试standard分词效果
$ curl -XGET 'http://127.0.0.1:9200/user/_analyze?analyzer= standard&pretty=true' -d '{"text":"中华人民共和国国歌"}'
# 测试ik分词效果
$ curl -XGET 'http://127.0.0.1:9200/user/_analyze?analyzer=ik&pretty=true' -d '{"text":"中华人民共和国国歌"}'
# 测试ik_smart分词效果
$ curl -XGET 'http://127.0.0.1:9200/user/_analyze?analyzer=ik_smart&pretty=true' -d '{"text":"中华人民共和国国歌"}'

ElasticSearch环境搭建(1.x版本)

基本步骤如下(ES版本是1.7.x)

  1. 安装Java环境
  2. 安装ES
  3. 安装head,bigdesk插件
  4. 安装ik分词插件
  5. 安装jdbc-river插件
  6. 使用ESClient测试

安装Java环境

1
2
3
4
5
6
7
8
9
10
11
12
# 下载安装JDK7,具体JDK的下载地址不提供了
$ tar -zxvf jdk-7u71-linux-x64.tar.gz
$ mkdir /usr/local/java/
$ cp -r jdk1.7.0_67 /usr/local/java/
# 配置Java环境变量
$ vi /etc/profile
# 添加Java环境变量
export JAVA_HOME=/usr/local/java/jdk1.7.0_67
export PATH=$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATH
export CLASSPATH=$CLASSPATH:.:$JAVA_HOME/lib:$JAVA_HOME/jre/lib

安装ES环境

1
2
3
4
5
6
7
8
9
10
11
12
# 下载安装ES 1.7.3
$ wget https://download.elastic.co/elasticsearch/elasticsearch/elasticsearch-1.7.3.tar.gz
$ tar -zxvf elasticsearch-1.7.3.tar.gz
$ mkdir /opt/
$ cp -r elasticsearch-1.7.3 /opt/
# 启动ES
$ cd /opt/elasticsearch-1.7.3/bin
$ ./elasticsearch
# 如果启动的时候报错,是没有权限创建log文件,尝试给ES_HOME目录的权限授予给当前用户
$ chown -R ben:root /opt/elasticsearch-1.7.3

安装ES插件head

1
2
3
4
5
6
7
8
9
10
11
# 两种安装方式(在线安装和离线安装)
# 注意:2.x和1.x版本的安装可能稍有不同,具体参考head的github
# 在线安装head插件
$ cd /opt/elasticsearch-1.7.3/bin
$ ./plugin --install mobz/elasticsearch-head
# 离线安装head插件
# 从github下载head插件的master.zip
$ cd /opt/elasticsearch-1.7.3/bin
$ ./plugin --install head --url file:///home/ben/Downloads/elasticsearch-head-master.zip

安装ik插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 从github下载ik插件,因为我使用的是老版本的ES,所以ik插件也使用的是对应老版本的
$ git clone https://github.com/medcl/elasticsearch-analysis-ik
$ cd elasticsearch-analysis-ik
$ mvn clean
$ mvn compile
$ mvn package
$ cd target
$ cp elasticsearch-analysis-ik-xxx.jar ${ES_HOME}/plugins/ik/
$ cd elasticsearch-analysis-ik
$ cp config/ik ${ES_HOME}/config/
# elasticsearch.yml配置文件中添加ik分词配置
index:
analysis:
analyzer:
ik:
alias: [ik_analyzer]
type: org.elasticsearch.index.analysis.IkAnalyzerProvider
ik_max_word:
type: ik
use_smart: false
ik_smart:
type: ik
use_smart: true
index.analysis.analyzer.default.type: ik
index.store.type: niofs

测试ik分词

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# User的mapping
{
"mappings": {
"user": {
"dynamic" : "strict",
"properties": {
"id": {
"type": "string",
"index": "not_analyzed"
},
"name": {
"type": "string",
"index_analyzer": "ik",
"search_analyzer": "ik_smart"
},
"age": {
"type": "integer"
},
"job": {
"type": "string",
"index_analyzer": "ik",
"search_analyzer": "ik_smart"
},
"createTime": {
"type": "long"
}
}
}
}
}
# 尝试创建user索引
$ curl -XPOST 'http://127.0.0.1:9200/user?pretty' -d '{
"mappings": {
"user": {
"dynamic" : "strict",
"properties": {
"id": {
"type": "string",
"index": "not_analyzed"
},
"name": {
"type": "string",
"index_analyzer": "ik",
"search_analyzer": "ik_smart"
},
"age": {
"type": "integer"
},
"job": {
"type": "string",
"index_analyzer": "ik",
"search_analyzer": "ik_smart"
},
"createTime": {
"type": "long"
}
}
}
}
}'
# 注意执行curl命令时,如果遇到下面的错误,原因是${ES_HOME}/lib/下需要引入httpclient-4.5.jar, httpcore-4.4.1.jar
{
"error" : "IndexCreationException[[user] failed to create index]; nested: NoClassDefFoundError[org/apache/http/client/ClientProtocolException]; nested: ClassNotFoundException[org.apache.http.client.ClientProtocolException]; ",
"status" : 500
}
# 创建索引成功后,查看索引信息
$ curl -XGET 'http://127.0.0.1:9200/_cat/indices?pretty'
green open user 5 1 0 0 970b 575b
# 测试standard分词效果
$ curl -XGET 'http://127.0.0.1:9200/user/_analyze?analyzer= standard&pretty=true' -d '{"text":"中华人民共和国国歌"}'
# 测试ik分词效果
$ curl -XGET 'http://127.0.0.1:9200/user/_analyze?analyzer=ik&pretty=true' -d '{"text":"中华人民共和国国歌"}'
# 测试ik_smart分词效果
$ curl -XGET 'http://127.0.0.1:9200/user/_analyze?analyzer=ik_smart&pretty=true' -d '{"text":"中华人民共和国国歌"}'

安装jdbc-river插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 下载安装jdbc-river
# 由于es官网叫停river类的导入插件,因此原始的elasticsearch-jdbc-river变更为elasticsearch-jdbc,成为一个独立的导入工具。官方提到的同类型工具还有logstash。
# 注意jdbc-river在1.5之前的版本和之后的版本有很大的区别,版本对应关系请参考:https://github.com/jprante/elasticsearch-jdbc
# 这里安装的1.7.3.0版本对应ES的1.7.3版本
# 安装方式也有所变化,1.5.0版本之后只需要直接下载zip包解压即可,以前的命令安装方式已经不再适用,所以不要使用下面的命令安装了
./bin/plugin --install river-jdbc --url http://xbib.org/repository/org/xbib/elasticsearch/plugin/elasticsearch-river-jdbc/1.5.0.5/elasticsearch-river-jdbc-1.5.0.5-plugin.zip
# 1.5版本之后改为jdbc-importer,具体的导入配置区别请参考:https://github.com/jprante/elasticsearch-jdbc/wiki/JDBC-plugin-feeder-mode-as-an-alternative-to-the-deprecated-Elasticsearch-River-API
$ wget http://xbib.org/repository/org/xbib/elasticsearch/importer/elasticsearch-jdbc/1.7.3.0/elasticsearch-jdbc-1.7.3.0-dist.zip
$ unzip elasticsearch-jdbc-1.7.3.0-dist.zip
$ cp -r elasticsearch-jdbc-1.7.3.0 /opt/
$ cd /opt/elasticsearch-1.7.3/bin
$ mkdir jdbc_importer
$ cd jdbc_importer
# 注意:执行该sh脚本的时候,需要给root账号开启远程访问权限
# 编辑mysql_user.sh脚本,用来全量同步MySQL的数据到ES
$ vi mysql_user.sh
# 编辑mysql_user_schedule.sh脚本,用来增量同步MySQL的数据到ES
$ vi mysql_user_schedule.sh
mysql_user.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/bash
bin=/opt/elasticsearch-jdbc-1.7.3.0/bin
lib=/opt/elasticsearch-jdbc-1.7.3.0/lib
echo '
{
"type" : "jdbc",
"jdbc" : {
"url" : "jdbc:mysql://127.0.0.1:3306/test",
"user" : "root",
"password" : "root",
"sql" : "select t.id as _id, t.id as id, t.name as name, t.age as age, t.job as job, t.createTime as createTime from user t",
"elasticsearch.cluster" : "es",
"elasticsearch.host" : "127.0.0.1:9300",
"index" : "user",
"type" : "user"
}
}
' | java \
-cp "${lib}/*" \
-Dlog4j.configurationFile="${bin}/log4j2.xml" \
"org.xbib.tools.Runner" \
"org.xbib.tools.JDBCImporter"
mysql_user_schedule.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/bin/bash
bin=/opt/elasticsearch-jdbc-1.7.3.0/bin
lib=/opt/elasticsearch-jdbc-1.7.3.0/lib
echo '
{
"type" : "jdbc",
"jdbc" : {
"url" : "jdbc:mysql://127.0.0.1:3306/test",
"user" : "root",
"password" : "root",
"schedule" : "0 0/1 * * * ?",
"interval" : 1,
"sql" : [
{
"statement" : "select t.id as _id, t.id as id, t.name as name, t.age as age, t.job as job, t.createTime as createTime from user t where t.createTime > ?",
"parameter" : [ "$metrics.lastexecutionstart" ]
}
],
"elasticsearch.cluster" : "es",
"elasticsearch.host" : "127.0.0.1:9300",
"index" : "user",
"type" : "user"
}
}
' | java \
-cp "${lib}/*" \
-Dlog4j.configurationFile="${bin}/log4j2.xml" \
"org.xbib.tools.Runner" \
"org.xbib.tools.JDBCImporter"

使用ESClient测试

后续完善