Winse Blog

走走停停, 熙熙攘攘, 忙忙碌碌, 不知何畏.

Logstash采集网站的访问日志

最近又重新接触了一下elastisearch、logstash、kibana,蛮好用的一个日志框架。

同时好久没有更新网站内容、也没怎么关注,虽然有cnzz(umeng)的日志统计功能,但是毕竟是很小一段时间的。要是能够把日志都导出来,就可以用ELK来分析一下自己网站一年来文章的访问情况。

嗯,前阵子买了阿里云的一个VPN服务器,正好可以利用利用。把访问的日志情况通过http发送给logstash,然后存储下来,等过一段时间我们再回来分析分析这些日志。^^

启动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
31
32
33
34
~/logstash-6.1.2/bin/logstash -e '
input { 
  http { 
    port => 20000 
    response_headers => {
      "Access-Control-Allow-Origin" => "*"
      "Content-Type" => "application/json"
      "Access-Control-Allow-Headers" => "Origin, X-Requested-With, Content-Type, Accept"
    }
  } 
} 
filter {
  if [message] =~ /^\s*$/ {
    drop { }
  }
  
  json {
    source => "message"
  }
  json {
    source => "location"
    target => "location"
  }
  mutate {
    remove_field => [ "headers" ]
  }
}
output { 
  file { 
    path => "winse-accesslog-%{+YYYY-MM-dd}.log"
    codec => json_lines 
  } 
} 
'

页面发送访问日志记录

1
2
3
4
5
6
7
8
9
10
11
12
$.ajax({
  type: "POST",
  url: "http://SERVER:PORT",
  data: JSON.stringify({
    title: document.title,
    location: JSON.stringify(location),
    referrer: document.referrer,
    userAgent: navigator.userAgent
  }),
  contentType: "application/json; charset=utf-8",
  dataType: "json"
});

–END

Gitalk on Octopress

以前有添加过 多说 ,步骤都类似的。其实就是调用一个第三方的服务,把评论的数据存储在第三方。

可以先看看 gitalk 的文档 ,分四步:

  • 注册一个github 的 OAuth Apps
  • 添加div容器
  • 加入css,js依赖
  • 调用javascript显示

配置

注册一个github应用

_layouts/post.html 的 Comments 下添加一个 gitalk-container 的节点:

(粘贴后把大括号和百分号之间的空格去掉)

1
2
3
4
5
6
7
8
{ % if site.disqus_short_name and page.comments == true % }
  <section>
    <h1>Comments</h1>
<!-- gitalk评论 start -->
    <div id="gitalk-container"></div> 
<!-- gitalk评论 end -->
  </section>
{ % endif % }

_includes 目录下增加一个 gitalk.html 的页面,添加依赖并添加初始化代码:

这里clientID,clientSecret对应第一步注册应用的id和secret。

在官网文档给的例子上调整了一下: id, body, createIssueManually。代码里面是通过 labels + id 来查询对应的issue:查询Issue源码

(粘贴后把大括号和百分号之间的空格去掉)

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
{ % if site.disqus_short_name and page.comments != false % }

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.css">
<script src="https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.min.js"></script>

<script>

var gitalk = new Gitalk({
  clientID: 'c14f68eac6330d15d984',
  clientSecret: '73b7c1fffa98e299ff0cdd332821201933858e6e',
  repo: 'winse.github.com',
  owner: 'winse',
  admin: ['winse'],
  id: location.pathname,
  labels: ['Gitalk'],
  body: "http://winseliu.com" + location.pathname,
  createIssueManually: true,
  
  // facebook-like distraction free mode
  distractionFreeMode: false
})

gitalk.render('gitalk-container')

</script>

{ % endif % }

然后在同一级目录的 after_footer.html 新增一条 这个新页面一个引用(粘贴后把大括号和百分号之间的空格去掉):

1
{ % include gitalk.html % }

初始化

其实就是在对应的repo下面建一个repo,注意下 labels 规则就行了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
username = "winse" # GitHub 用户名

# https://github.com/settings/tokens
new_token = ""  # GitHub Token
repo_name = "winse.github.com" # 存放 issues

sitemap_url = "sitemap.xml" # sitemap
kind = "Gitalk"

# 可以结合git的状态,added的调用命令创建一个issue

#除了使用Token,也可以手动输入密码
curl -H "Accept: application/json" -X POST -d '{"body": "http://winseliu.com/blog/2017/11/20/sed-debug-sedsed/", "labels": ["Gitalk", "/blog/2017/11/20/sed-debug-sedsed/"], "title": "gitalk 测试" }' -u $username https://api.github.com/repos/$username/$repo_name/issues
Enter host password for user 'winse':

参考

–END

Sed Debug: Sedsed

上一篇把html转成rst,但是页面之间的链接都断了。需要在标题前加上一个TAG,最终效果如下:

1
2
3
4
5
6
7

.. _Creating Objects in New Mappings:

Creating Objects in New Mappings
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

+ :ref:`Creating Objects in New Mappings`

想使用sed来实现这个功能,需要利用一些sed的高级功能,但默认sed是不能调试。这里使用sedsed来查看每一个操作的模式空间和缓冲空间,有点类似print调试。对于理解 sed 很有帮助,特别是对理解缓冲区和模式区数据的处理。

安装 sedsed

1
2
cd /opt/
git clone https://github.com/aureliojargas/sedsed

看看实际的调试输出:

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
[root@ansible sedsed]# echo -e "one\ntwo\nthree\nfour" | ./sedsed.py -d -f test/scripts/sort.gnu.sed 
PATT:one$
HOLD:$
COMM:H
PATT:one$
HOLD:\none$
COMM:$ !d
PATT:two$
HOLD:\none$
COMM:H
PATT:two$
HOLD:\none\ntwo$
COMM:$ !d
PATT:three$
HOLD:\none\ntwo$
COMM:H
PATT:three$
HOLD:\none\ntwo\nthree$
COMM:$ !d
PATT:four$
HOLD:\none\ntwo\nthree$
COMM:H
PATT:four$
HOLD:\none\ntwo\nthree\nfour$
COMM:$ !d
PATT:four$
HOLD:\none\ntwo\nthree\nfour$
COMM:g
PATT:\none\ntwo\nthree\nfour$
HOLD:\none\ntwo\nthree\nfour$
COMM:s/.//
PATT:one\ntwo\nthree\nfour$
HOLD:\none\ntwo\nthree\nfour$
COMM:s/\n/&L&l/g
PATT:one\nL\nltwo\nL\nlthree\nL\nlfour$
HOLD:\none\ntwo\nthree\nfour$
COMM:s/^/\\Na/
PATT:\naone\nL\nltwo\nL\nlthree\nL\nlfour$
HOLD:\none\ntwo\nthree\nfour$
COMM:s/\nL/\\NA/
PATT:\naone\nA\nltwo\nL\nlthree\nL\nlfour$
HOLD:\none\ntwo\nthree\nfour$
COMM:s/$/\\NL/
PATT:\naone\nA\nltwo\nL\nlthree\nL\nlfour\nL$
HOLD:\none\ntwo\nthree\nfour$
COMM:b start
COMM:/\nA$/ b exit
COMM:s/\nb/\\Nl/
PATT:\naone\nA\nltwo\nL\nlthree\nL\nlfour\nL$
HOLD:\none\ntwo\nthree\nfour$
COMM:s/\nB/\\NL/
PATT:\naone\nA\nltwo\nL\nlthree\nL\nlfour\nL$
HOLD:\none\ntwo\nthree\nfour$
COMM:s/\(\na.*\nA\)\nl\([^\n]*\)\nL/\1\\Nb\2\\NB/
PATT:\naone\nA\nbtwo\nB\nlthree\nL\nlfour\nL$
HOLD:\none\ntwo\nthree\nfour$
COMM::sort
COMM:h
......

[root@ansible sedsed]# (date +'%w %d' ; date +'%-m %Y') | ./sedsed.py -d -f test/scripts/cal.sed
......

网上的一案例

看到一个论坛帖子上用sed实现 删除匹配的前两行和后三行 ,看的不是很明白,帖子仅注意介绍流程,至于数据到底是怎么样的没有讲。如果知道 sedsed 这工具的话,运行一遍就全部清楚了:

sedsed.py 处理 + 加号有点问题,所以这里就处理匹配的前两行,看看具体的数据是怎么流转的:

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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
[root@ansible sedsed]# echo -e "1\n2\n3\n4\n5\n6\n7\n8\n9\n10" | ./sedsed.py -d '/5/d;:go;1,2!{P;N;D};N;bgo' 
PATT:1$
HOLD:$
COMM:/5/ d
PATT:1$
HOLD:$
COMM::go
COMM:1,2 !{
COMM:N
PATT:1\n2$
HOLD:$
COMM:b go
COMM:1,2 !{
COMM:N
PATT:1\n2\n3$
HOLD:$
COMM:b go
COMM:1,2 !{
COMM:P
1
PATT:1\n2\n3$
HOLD:$
COMM:N
PATT:1\n2\n3\n4$
HOLD:$
COMM:D
PATT:2\n3\n4$
HOLD:$
COMM:/5/ d
PATT:2\n3\n4$
HOLD:$
COMM::go
COMM:1,2 !{
COMM:P
2
PATT:2\n3\n4$
HOLD:$
COMM:N
PATT:2\n3\n4\n5$
HOLD:$
COMM:D
PATT:3\n4\n5$
HOLD:$
COMM:/5/ d
PATT:6$
HOLD:$
COMM:/5/ d
PATT:6$
HOLD:$
COMM::go
COMM:1,2 !{
COMM:P
6
PATT:6$
HOLD:$
COMM:N
PATT:6\n7$
HOLD:$
COMM:D
PATT:7$
HOLD:$
COMM:/5/ d
PATT:7$
HOLD:$
COMM::go
COMM:1,2 !{
COMM:P
7
PATT:7$
HOLD:$
COMM:N
PATT:7\n8$
HOLD:$
COMM:D
PATT:8$
HOLD:$
COMM:/5/ d
PATT:8$
HOLD:$
COMM::go
COMM:1,2 !{
COMM:P
8
PATT:8$
HOLD:$
COMM:N
PATT:8\n9$
HOLD:$
COMM:D
PATT:9$
HOLD:$
COMM:/5/ d
PATT:9$
HOLD:$
COMM::go
COMM:1,2 !{
COMM:P
9
PATT:9$
HOLD:$
COMM:N
PATT:9\n10$
HOLD:$
COMM:D
PATT:10$
HOLD:$
COMM:/5/ d
PATT:10$
HOLD:$
COMM::go
COMM:1,2 !{
COMM:P
10
PATT:10$
HOLD:$
COMM:N
10

可以看到 PATT 模式空间把前面两行连到一起了,匹配到 5 的时刻其实模式空间的内容为 3\n4\n5,然后执行 d 这就相当于删除前两行了。

该命令会多输出最后一行:由于到最后一行,N 又读取了一次下一行(读到结束符),直接就返回没有执行 D 了。sed 文档中的描述如下:

1
2
3
4
5
6
7
8
9
10
  `D'
      If pattern space contains no newline, start a normal new cycle as
      if the `d' command was issued.  Otherwise, delete text in the
      pattern space up to the first newline, and restart cycle with the
      resultant pattern space, without reading a new line of input.
  
  `N'
      Add a newline to the pattern space, then append the next line of
      input to the pattern space.  If there is no more input then `sed'
      exits without processing any more commands.

修复就是:读到最后一行的时刻就不读下一行了:

1
[root@ansible sedsed]# echo -e "1\n2\n3\n4\n5\n6\n7\n8\n9\n10" | sed '/5/,+3d;:go;1,2!{P;$!N;D};N;bgo' 

加标签

Sphinx可以通过 ref 来访问整个文档中定义的标签。所以只需要在每个标题前加上TAG,然后把链接引用修改成 ref 的方式即可。

1
2
3
4
5
6
7
# 文档加TAG:
sed -i ' h;N; /\n=\+$/{ x;s/.*/.. _&:\n/;p; x };  P;D ' $(find . -name '*.rst')
sed -i ' h;N; /\n-\+$/{ x;s/.*/.. _&:\n/;p; x };  P;D ' $(find . -name '*.rst')


# 修改链接引用:
sed 's/\(`[[:alnum:] ]*`\)_/:ref:\1/ ' $(find . -name '*.rst')

–END

Gitlab on Docker

1
2
3
4
5
6
7
8
9
10
11
12
13
./docker-download-mirror.sh sameersbn/redis sameersbn/gitlab:10.1.4 sameersbn/postgresql:9.6-2

# 最好,每个 docker-compose.yml 放不同的目录名称下!!
mkdir gitlab
cd !*

wget https://raw.githubusercontent.com/sameersbn/docker-gitlab/master/docker-compose.yml
sed -i '/GITLAB_HOST/s/.*/    - GITLAB_HOST=192.168.193.8/' docker-compose.yml 

docker-compose up -d

firewall-cmd --zone=public --add-port=80/tcp --permanent
firewall-cmd --reload

–END

使用Sphinx生成/管理文档

很多开源的软件都使用Sphinx来进行文档的管理,其中Ansible就是其中一个。

Sphinx使用 类MarkDown的reStructuredText格式 来进行内容的编写,然后使用 sphinx-build 命令来生成html文件。

安装、入门

1
2
3
4
sudo apt-get install python-pip
sudo pip install Sphinx

sphinx-quickstart

引用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

*重点(emphasis)通常显示为斜体*
`解释文字(interpreted text)通常显示为斜体`

**重点强调(strong emphasis)通常显示为粗体**

``行内文本(inline literal)通常显示为等宽文本,空格可以保留,但是换行不可以。``

章节头部由下线(也可有上线)和包含标点的标题 组合创建, 其中下线要至少等于标准文本的长度。
可以表示标题的符号有 =、-、`、:、'、"、~、^、_ 、* 、+、 #、<、> 。
对于相同的符号,有上标是一级标题,没有上标是二级标题。
标题最多分六级,可以自由组合使用。

# with overline, for parts
* with overline, for chapters
=, for sections
-, for subsections
^, for subsubsections
", for paragraphs

主题

1
2
3
sudo pip install sphinx_rtd_theme

sed -i "/html_theme/s/.*/html_theme = 'sphinx_rtd_theme'/" conf.py

管理历史文档

先使用 html2rest 把html转成reStructuredText格式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sudo pip install html2rest

#JSON:原始文档层次结构
  [
  { "id": "a16", "pId": "a", "name": "Administration", "file": "output/AdministrativeDocumentation.html" }, 
  { "id": "a1617", "pId": "a16", "name": "Basic Configuration Guide" },
  { "id": "a161718", "pId": "a1617", "name": "Configuring Deployments", "file": "output/ConfiguringDeployments.html" }
  ]


name=administration
cat $name.json | jq '.[].file' | sed 's/"//g' | while read line ; do cp "$line" $name.origin/  ; done
cd $name.origin
ls | while read f ; do html2rest $f >"../$name.rst/${f%%.*}.rst" ; done

这仅仅是把html转换成了reStructuredText格式,当然我们还可以做多一些的操作:把文件结构也创建出来。

docs-gen.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
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
#!/bin/bash

JSON_FILE=~/administration.json

function children(){
local id=$1

local name="$( cat "$JSON_FILE" | jq '.[] | select(.id=="'$id'")' | jq '.name' | sed 's/"//g' )"
echo "id: $id, name: $name"

local filename="$( echo $name | sed 's/[^[:alnum:]]//g' )"

if [ ! -f "$filename.rst" ] ; then
cat > "$filename.rst" <<EOF
$name
======================================

EOF
fi

local nodes="$( cat "$JSON_FILE" | jq '.[] | select(.pId=="'$id'")' )"

if [ "x$nodes" == "x" ] ; then 
  return 1
fi

# if have children, create folder and toc
local foldername="$( echo $name | sed 's/[^[:alnum:]]//g' )"
local names="$( echo "$nodes" | jq ".name" | sed 's/[^[:alnum:]]//g' )"
local ids="$( echo "$nodes" | jq ".id" | sed 's/[^[:alnum:]]//g' )"

if ! grep '.. toctree::' "$foldername.rst" ; then
cat >>"$foldername.rst" <<EOF

Contents:

.. toctree::
   :maxdepth: 3
   :titlesonly:
   :hidden:
   :glob:
   
$( echo "$names" | sed "s#^#   $foldername/#" ) 

EOF
fi

mkdir -p "$foldername"
pushd "$foldername"

while read cid
do 
  children $cid
done < <(echo "$ids")

popd

}


children a

然后执行该命令,把目录、目录索引、临时文件创建好:

1
2
cd ~/administration
./docs-gen.sh

然后就是把最开始转换的rst文件拷贝过来:

1
2
3
4
5
6
7
8
9
10
cd ../administration.rst

ls | while read f ; do 
filename="$(echo $f | sed 's/.rst$//' | sed 's/[^[:alnum:]]//g' ).rst" ; 
find ../administration/ -name "$filename" -exec /bin/cp -f $f {} \;  ;  
done

#再执行一遍docs-gen.sh,把目录的索引再(确认)添加一次文件末尾
cd ../administration
./docs-gen.sh

完后生成 make html ,直接打开 _build/html/index.html 查看下内容。

最后就是根据具体情况,做一些细微的调整了。

  • 处理图片,修改 /usr/local/lib/python2.7/dist-packages/html2rest.py
  • 处理文档内互相引用的链接
  • 给标题添加TAG

生成PDF

除了生成html外,还可以直接编译成PDF,方便携带和查看。(官网是推荐使用latexpdf,但这得安装latex…)

1
2
3
4
5
6
7
8
9
10
[root@ansible workspace]# pip install rst2pdf

[root@ansible workspace]# vi conf.py 
...
#extensions = ['sphinx.ext.doctest', 'sphinx.ext.todo', 'sphinx.ext.pngmath']
extensions = ['sphinx.ext.doctest', 'sphinx.ext.todo', 'rst2pdf.pdfbuilder']

pdf_documents = [('index', u'Workspace', u'Workspace Doc', u'winse'),]

[root@ansible workspace]# sphinx-build -b pdf . _build/pdf

或者用 singlehtml 临时代替下也行。

1
make singlehtml

MISC

–END