├── README.md
├── assets
└── images
│ ├── grafana.png
│ ├── rabbitMQ.png
│ ├── structure.png
│ └── supervisor.png
├── clickhouse
├── log_php_error.sql
├── log_request.sql
├── log_sql.sql
└── log_syslog.sql
├── logrotate
└── my-app
├── php
├── composer.json
├── config.php
├── functions.php
├── retention_log.php
├── test_app_log.php
├── udp_server.php
└── udp_worker.php
├── supervisor
├── http_server.conf
├── udp_server.conf
└── udp_worker.conf
└── syslog-ng
└── remote_host.conf
/README.md:
--------------------------------------------------------------------------------
1 | # Tạo một hệ thống Logging cho nhiều dự án/server
2 |
3 | PS: Bài viết góp nhặt từ kinh nghiệm và học hỏi từ các chuyên gia
4 |
5 | Chào các bạn !
6 |
7 | Cái gì cũng vậy, đều đến từ nhu cầu thực tế, nhu cầu của mình là cần có 1 nơi lưu lại các Log của những webApp/website/server mình đang quản lý và hiển thị báo cáo thành Dashboard cho dễ nhìn.
8 |
9 | Ở đây còn mang ý nghĩa là đo lường hiệu năng của các script chạy trên đó như là tốt độ load/lỗi tự nhiên xuất hiện, hoặc tốc độ từng câu SQL, cái này hay gặp mới lên Production thì chạy nhanh, cỡ 6 tháng sau thì cả hệ thống như con rùa là do SQL :p
10 |
11 | Đặc biệt mấy cái lỗi này thì không có ai báo bạn biết đâu à, mấy tháng sau mới phát hiện thì căng quá.
12 |
13 | Mình cũng đã thử khá nhiều dịch vụ quốc tế như loggly.com, logentries.com,... túm lại là chưa tới đâu, phần là vì chi phí, phần là không đúng nhu cầu.
14 |
15 | Cuối cùng sao ? thì theo dõi log bằng cơm o.O xài mấy dịch vụ theo dõi server sống hay chết,...
16 |
17 | Cho đến 1 ngày, idol của mình là bạn Võ Duy Tuấn, làm 1 buổi meetup về [Xây dựng hệ thống Log cho Microservices](http://bloghoctap.com/technology/xay-dung-he-thong-log-cho-microservices.html)
18 |
19 | Đúng bài luôn :D đi xin source mãi mà ảnh không cho, nên đành chịu, tự xem theo cấu trúc hệ thống mà tự dựng vậy Y_Y
20 |
21 | Làm xong rồi, chạy rồi, nên mình mới viết bài này, vừa lưu lại, vừa ôn bài, vừa share cho anh em đồng đạo nếu có nhu cầu tương tự.
22 |
23 | ## Mô tả cấu trúc hệ thống (hình của anh Tuấn)
24 |
25 | 
26 |
27 | ## Thứ tự việc cần làm
28 |
29 | 1. Log hệ thống bằng NodeQuery.com
30 | 2. Tạo server chứa và nhận Log
31 | 3. Cài đặt RabbitMQ
32 | 4. Cài đặt ClickHouse
33 | 5. Cài UDP Log Server
34 | 6. Cài UDP Log Worker
35 | 7. Cài Supervisor
36 | 8. Cài syslog-ng và gởi log remote
37 | 9. App Log và PHP Error
38 | 10. Cronjob Retention Log
39 | 11. Grafana vẽ Dashboard
40 | 12. Đoạn kết
41 |
42 |
43 | ## Log hệ thống bằng NodeQuery
44 |
45 | [NodeQuery.com](https://nodequery.com)
46 |
47 | Ok đăng ký 30s, gõ lệnh 5s, là bạn đã có 1 hệ thống theo dõi Ram, HDD, CPU cho server của mình.
48 |
49 | Free 10 server và có cả tính năng Alert qua e-mail
50 |
51 | Tool quá xịn, mong chờ bấy lâu :D đặc biệt vụ hết HDD là hay gặp ở server Cloud
52 |
53 | ## Tạo server chứa và nhận Log
54 |
55 | Mình chọn [Digital Ocean](https://www.digitalocean.com/) giá hạt dẻ 5$/tháng RAM 1 GB, 1 vCPU, 1 TB traffic, 25 GB SSD
56 |
57 | Cách dễ là chọn Image Ubuntu LAMP 18.04 là xong. Hoặc thuần Ubnuntu 18.04 rồi cài thêm PHP.
58 |
59 | ## Cài đặt RabbitMQ
60 |
61 | Mình để sẵn các command line hoặc xem [link](https://computingforgeeks.com/how-to-install-latest-rabbitmq-server-on-ubuntu-18-04-lts/)
62 |
63 | Cài Erlang xem [link](https://computingforgeeks.com/how-to-install-latest-erlang-on-ubuntu-18-04-lts/)
64 |
65 | Cài RabbitMQ
66 | > wget -O- https://dl.bintray.com/rabbitmq/Keys/rabbitmq-release-signing-key.asc | sudo apt-key add -
67 |
68 | > wget -O- https://www.rabbitmq.com/rabbitmq-release-signing-key.asc | sudo apt-key add -
69 |
70 | > echo "deb https://dl.bintray.com/rabbitmq/debian $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/rabbitmq.list
71 |
72 | > sudo apt update
73 |
74 | > sudo apt -y install rabbitmq-server
75 |
76 | > sudo systemctl status rabbitmq-server.service
77 |
78 | > systemctl is-enabled rabbitmq-server.service
79 |
80 | Kiểm tra ok chưa
81 | > sudo rabbitmqctl status
82 |
83 | Bật chế độ xem giao diện quản lý RabbitMQ bằng web browser
84 | > sudo rabbitmq-plugins enable rabbitmq_management
85 |
86 | > sudo chown -R rabbitmq:rabbitmq /var/lib/rabbitmq/
87 |
88 | Tạo tài khoản vào RabbitMQ (default là guest / guest)
89 |
90 | XXXpasswordXXX là mật khẩu của admin
91 | > sudo rabbitmqctl add_user admin XXXpasswordXXX
92 |
93 | > sudo rabbitmqctl set_user_tags admin administrator
94 |
95 | > sudo rabbitmqctl set_permissions -p / admin ".\*" ".\*" ".\*"
96 |
97 | Tạo user log, dùng để UDP Worker lấy log về Database
98 |
99 | > sudo rabbitmqctl add_user log XXXpasswordXXX
100 |
101 | > sudo rabbitmqctl set_user_tags log administrator
102 |
103 | > sudo rabbitmqctl set_permissions -p / log ".\*" ".\*" ".\*"
104 |
105 | > sudo ufw allow proto tcp from any to any port 5672,15672
106 |
107 | Done rồi
108 |
109 | Giờ truy cập vào địa chỉ http://[IP Log server]:15672/
110 |
111 | Nếu thấy hình này thì là ok nha. Không thì xem lại link tài liệu ở trên.
112 |
113 | 
114 |
115 | Cách sử dụng thì các bạn xem tài liệu của [RabbitMQ tại đây](https://www.rabbitmq.com/)
116 |
117 | ## Cài đặt ClickHouse
118 |
119 | Theo anh Tuấn giới thiệu thì Database ClickHouse query xử lý tính bằng trăm triệu records. Hoạt động trên cơ chế column-oriented thay vì row-oriented như MySQL, xem thêm sự khác nhau [ở đây](https://www.geeksforgeeks.org/difference-between-row-oriented-and-column-oriented-data-stores-in-dbms/)
120 |
121 | Xem thêm hướng dẫn cài đặt [ở đây](https://www.digitalocean.com/community/tutorials/how-to-install-and-use-clickhouse-on-ubuntu-18-04)
122 |
123 | > sudo apt-key adv --keyserver keyserver.ubuntu.com --recv E0C56BD4
124 |
125 | > echo "deb http://repo.yandex.ru/clickhouse/deb/stable/ main/" | sudo tee /etc/apt/sources.list.d/clickhouse.list
126 |
127 | > sudo apt-get update
128 |
129 | > sudo apt-get install -y clickhouse-server clickhouse-client
130 |
131 | > sudo service clickhouse-server start
132 |
133 | > sudo service clickhouse-server status
134 |
135 | > sudo ufw allow proto tcp from any to any port 8123,9000
136 |
137 | Mình dùng App [DBeaver](https://dbeaver.io/) để kết nối vào ClickHouse
138 |
139 | Schema để trong thư mục clickhouse (anh Tuấn share)
140 |
141 | Chi tiết cách query, select/insert/update/delete thì các bạn đọc tài liệu ClickHouse nhé.
142 |
143 | ## Cài UDP Log Server
144 |
145 | Trước tiên xác định là mình sử dụng PHP command line để tạo 1 UDP server, nhớ giấu IP server Log này hoặc bạn tự tìm hiểu thêm cách bảo vệ nhé.
146 |
147 | Sử dụng extension Swoole
148 |
149 | > pecl install swoole
150 |
151 | > touch /etc/php/7.2/cli/conf.d/swoole.ini && echo 'extension=swoole.so' > /etc/php/7.2/cli/conf.d/swoole.ini
152 |
153 | Kiểm tra
154 | > php -m | grep swoole
155 |
156 | Source code server UDP có ở thư mục php nhé.
157 | Bạn đưa lên server ví dụ /var/www cũng được
158 |
159 | Chạy command line sau ở thư mục php
160 |
161 | > composer install
162 |
163 | > sudo ufw allow proto udp from any to any port 9502,9501
164 |
165 | Chạy thử
166 | > php /var/www/udp_server.php
167 |
168 | Cập nhật file config.php
169 | File này chứa các thông số như IP, port, username, password của RabbitMQ và ClickHouse
170 | Bạn cần cập nhật đầy đủ, chú ý coi chừng nhầm giữa port 15672 dành cho RabittMQ web management và 5672 dành cho file config để kết nối TCP, trong file config default là 5672
171 |
172 | ## Cài UDP Log Worker
173 |
174 | Cái này chỉ là 1 script PHP dùng để đọc dữ liệu trong queue trong RabbitMQ và insert vào database ClickHouse
175 |
176 | Các bạn chú ý trong file này có chia ra 3 loại log (còn thiếu SQL chưa viết xong :p) tương ứng đưa vào 3 table khác nhau.
177 |
178 | - Log từ App (file app.log)
179 | - Log từ PHP Error (Warning, Fatal Error,...)
180 | - Log từ syslog khác
181 |
182 | Worker và UDP Server đều chạy thường trực.
183 |
184 | Chạy thử
185 | > php /var/www/udp_worker.php
186 |
187 | Để kiểm tra hoặc giữ cho script luôn chạy thì mình dùng Supervisor
188 |
189 | ## Cài Supervisor
190 |
191 | [Link cài đặt](https://flotz-chronicles.com/script/php/2015/08/18/running-php-script-forever-with-supervisor.html)
192 |
193 | > sudo apt-get install supervisor
194 |
195 | Copy 2 file udp_server.conf và udp_worker.conf vào thư mục /etc/supervisor/conf.d/
196 |
197 | > sudo service supervisor restart
198 |
199 | Nếu bạn có nhu cầu để xem trên Browser làm cái này
200 | 
201 | Copy file supervisor/http_server.conf vào thư mục /etc/supervisor/conf.d/
202 |
203 | > sudo ufw allow proto tcp from any to any port 9001
204 |
205 | ## Cài syslog-ng và gởi log remote trên server dự án
206 |
207 | Cài đặt syslog-ng và cấu hình để gởi log đến server UDP của mình.
208 | *Lưu ý chỗ này là server dự án chứ không phải server log*
209 |
210 | > sudo apt-get install syslog-ng -y
211 |
212 | Điền IP Server UDP vào chỗ IP_UDP_LOG_SERVER trong file syslog-ng/remote_host.conf
213 |
214 | Trong file này, bạn sẽ thấy mình lấy access.log, error.log và app.log
215 | Trong đó access.log và error.log là của Apache tạo, còn app.log là do web application của bạn tạo.
216 |
217 | Copy file remote_host.conf vào /etc/syslog-ng/conf.d/
218 |
219 | > service syslog-ng restart
220 |
221 | Chỗ này mình gặp 1 vấn đề là PHP không tạo được file log trong /var/log/apache2, vì vậy mình cần để log của app ở nơi khác, chỗ mà user www-data truy cập được.
222 |
223 | > mkdir /var/www/log
224 |
225 | > chown www-data:www-data /var/www/log
226 |
227 | > nano /etc/logrotate.d/my-app
228 |
229 | Copy nội dung file logrotate/myapp
230 |
231 | Ý nghĩa việc này là cấp quyền user www-data được đọc file log và có chế độ rotate log
232 |
233 | ## App Log và PHP Error
234 |
235 | Đây là log chủ động, tức là log do Application của bạn tạo ra. Ở đây mình dùng Monolog và tạo ra file app.log
236 |
237 | Format template của Log do bạn quy định, sau đó ở UDP Worker bạn explode() để lấy các thông số mình cần.
238 |
239 | Bạn hãy xem file test_app_log.php để thấy cách mình làm.
240 |
241 | Ở file này sẽ log lại thời gian bắt đầu/kết thúc của script, kèm memory.
242 |
243 | Format Template dạng:
244 | > [2020-01-15T15:05:54.681827+07:00] APP-v1.0.INFO: [START2020-01-15 2020-01-15_08:05:54 08 05 APP-v1.0 index.php GET success 0.075027942657471 6291456 118.69.76.177END] [] []
245 |
246 | Về PHP Error, thì Apache sẽ báo lỗi 500, và sẽ lưu log về error.log
247 |
248 | Trong file udp_worker.php có phần detect PHP Error log để đưa vào bảng log_php_error.
249 |
250 | Sau đó chúng ta bắt đầu vẽ Dashboard bằng cách kết nối vào ClickHouse và query các Chart mình cần.
251 |
252 | ## Cronjob Retention Log
253 |
254 | Cập nhật Crontjob 1 tháng chạy 1 lần để tiến hành xóa Log theo định kỳ.
255 | Ở đây, data log lưu trong Clickhouse đang được chia theo Partition YYYYMM nên chúng ta sẽ xóa Partition của tháng trước
256 | > sudo crontab -e
257 |
258 | Dán nội dung
259 |
260 | > 0 0 1 * * php /var/www/html/retention_log.php
261 |
262 | ## Grafana vẽ Dashboard
263 |
264 | Tuấn có đề cập đến việc sử dụng tự dev ReactJS, hoặc dùng Grafana, Jaeger UI
265 |
266 | Trong bài này, mình chỉ đề cập đến [Grafana.com](https://grafana.com)
267 |
268 | Install Grafana, bạn có thể install trên server khác và kết nối vào ClickHouse
269 |
270 | > sudo apt-get install -y apt-transport-https
271 |
272 | > sudo apt-get install -y software-properties-common wget
273 |
274 | > wget -q -O - https://packages.grafana.com/gpg.key | sudo apt-key add -
275 |
276 | > sudo add-apt-repository "deb https://packages.grafana.com/oss/deb stable main"
277 |
278 | > sudo apt-get update
279 |
280 | > sudo apt-get install grafana
281 |
282 | Start Grafana Server
283 |
284 | > sudo systemctl daemon-reload
285 |
286 | > sudo systemctl start grafana-server
287 |
288 | > sudo systemctl status grafana-server
289 |
290 | Mở port firewall
291 |
292 | > sudo ufw allow proto tcp from any to any port 3000
293 |
294 | Truy cập http://[IP_LOG_SERVER]:3000
295 |
296 | Username mặc định là admin / admin, sẽ có yêu cầu đổi khi bạn đăng nhập vào.
297 |
298 | Cách sử dụng Grafana thì bạn xem trên website [Grafana.com](https://grafana.com)
299 |
300 | Hình demo của Tuấn
301 | 
302 |
303 | Ghi chú: Vì ghi log của rất nhiều server khác nhau, vì vậy trong Grafana có tính năng Variable dùng để filter theo tên Application khá tốt.
304 |
305 | ## Đoạn kết
306 |
307 | Trên đây là các kinh nghiệm đã làm qua, hy vọng sẽ giúp ích với các bạn có nhu cầu.
308 |
309 | Theo như idol Tuấn đề cập, thì bạn ấy cũng đã thử nhiều cách khác nhau mới chọn ra được cách này, mang lại sự ổn định nhất cho đến hiện tại. Số log lưu lại tính bằng trăm triệu records mà hệ thống vẫn chạy ổn định, kể cả server Log.
--------------------------------------------------------------------------------
/assets/images/grafana.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ngocquang/logging_system/1b80011fd5396dd8ea65002551049bac407085bc/assets/images/grafana.png
--------------------------------------------------------------------------------
/assets/images/rabbitMQ.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ngocquang/logging_system/1b80011fd5396dd8ea65002551049bac407085bc/assets/images/rabbitMQ.png
--------------------------------------------------------------------------------
/assets/images/structure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ngocquang/logging_system/1b80011fd5396dd8ea65002551049bac407085bc/assets/images/structure.png
--------------------------------------------------------------------------------
/assets/images/supervisor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ngocquang/logging_system/1b80011fd5396dd8ea65002551049bac407085bc/assets/images/supervisor.png
--------------------------------------------------------------------------------
/clickhouse/log_php_error.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE default.log_php_error (
2 | `lp_date` Date,
3 | `lp_datetime` DateTime,
4 | `lp_host` String,
5 | `lp_type` String,
6 | `lp_file` String,
7 | `lp_line` UInt32,
8 | `lp_message` String,
9 | `lp_ip` String
10 | ) ENGINE = MergeTree PARTITION BY toYYYYMM(`lp_date`) ORDER BY `lp_date`;
--------------------------------------------------------------------------------
/clickhouse/log_request.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE default.log_request (
2 | `lr_date` Date,
3 | `lr_datetime` DateTime('Etc/UTC'),
4 | `lr_hour` UInt8,
5 | `lr_minute` UInt8,
6 | `lr_application` String,
7 | `lr_module` String,
8 | `lr_action` String,
9 | `lr_status` String,
10 | `lr_exectime` Float32,
11 | `lr_memory` UInt32,
12 | `lr_ip` String,
13 | `lr_query_count` UInt16,
14 | `lr_query_time` Float32,
15 | `lr_agent` String
16 | ) ENGINE = MergeTree PARTITION BY toYYYYMM(`lr_date`) ORDER BY `lr_date`;
--------------------------------------------------------------------------------
/clickhouse/log_sql.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE default.log_sql (
2 | `ls_date` Date,
3 | `ls_datetime` DateTime,
4 | `ls_hour` UInt8,
5 | `ls_minute` UInt8,
6 | `ls_application` String,
7 | `ls_module` String,
8 | `ls_action` String,
9 | `ls_status` String,
10 | `ls_exectime` Float32,
11 | `ls_query_type` String,
12 | `ls_table` String,
13 | `ls_sql` String
14 | ) ENGINE = MergeTree PARTITION BY toYYYYMM(`ls_date`) ORDER BY `ls_date`;
--------------------------------------------------------------------------------
/clickhouse/log_syslog.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE default.log_syslog (
2 | `ls_date` Date,
3 | `ls_datetime` DateTime,
4 | `ls_message` String,
5 | `ls_tag` String,
6 | `ls_host` String
7 | ) ENGINE = MergeTree PARTITION BY toYYYYMM(`ls_date`) ORDER BY `ls_date`;
--------------------------------------------------------------------------------
/logrotate/my-app:
--------------------------------------------------------------------------------
1 | /var/www/log/*.log {
2 | daily
3 | maxsize 250M
4 | missingok
5 | rotate 14
6 | compress
7 | notifempty
8 | create 0640 www-data www-data
9 | sharedscripts
10 | postrotate
11 | systemctl reload my-app
12 | endscript
13 | }
--------------------------------------------------------------------------------
/php/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "require": {
3 | "php-amqplib/php-amqplib": ">=2.9.0",
4 | "smi2/phpclickhouse": "^1.3"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/php/config.php:
--------------------------------------------------------------------------------
1 | 'IP_LOG_SERVER',
14 | 'port' => 5672,
15 | 'user' => 'log',
16 | 'pass' => 'XpasswordX',
17 | 'vhost' => '/',
18 | 'queue_sys_log' => 'syslog' // Đây là Queue tùy bạn chọn
19 | ];
20 | // Config ClickHouse
21 | $config['clickhouse'] = [
22 | 'host' => 'localhost',
23 | 'port' => '8123',
24 | 'username' => 'default',
25 | 'password' => 'XpasswordX',
26 | 'database' => 'default', // Tên Database tùy bạn chọn
27 | 'log_request_table' => 'log_request', // Log Request từ App
28 | 'log_php_error_table' => 'log_php_error', // Log PHP Error
29 | 'log_syslog_table' => 'log_syslog', // Log hệ thống từ Apache,...
30 | ];
31 |
--------------------------------------------------------------------------------
/php/functions.php:
--------------------------------------------------------------------------------
1 | $errorDateTime,
79 | 'type' => $errorType,
80 | 'file' => $errorFile,
81 | 'line' => (int)$errorLine,
82 | 'message' => $errorMessage,
83 | 'ip_address' => $ipAddress,
84 | ];
85 | }
86 |
--------------------------------------------------------------------------------
/php/retention_log.php:
--------------------------------------------------------------------------------
1 | database($config['clickhouse']['database']);
8 |
9 |
10 | # Drop latest month
11 | $latest_month = date('Ym', strtotime("-2 month"));
12 |
13 | # Retention table log_request
14 | $db->write('
15 | ALTER TABLE default.log_request DROP PARTITION '.$latest_month.'
16 | ');
17 | # Retention table log_php_error
18 | $db->write('
19 | ALTER TABLE default.log_php_error DROP PARTITION '.$latest_month.'
20 | ');
21 | # Retention table log_syslog
22 | $db->write('
23 | ALTER TABLE default.log_syslog DROP PARTITION '.$latest_month.'
24 | ');
25 | # Retention table log_sql
26 | $db->write('
27 | ALTER TABLE default.log_sql DROP PARTITION '.$latest_month.'
28 | ');
29 |
30 | echo 'done';
31 |
--------------------------------------------------------------------------------
/php/test_app_log.php:
--------------------------------------------------------------------------------
1 | pushHandler(new StreamHandler('/var/www/log/app.log', Logger::DEBUG));
22 |
23 | $module_name = basename($_SERVER['PHP_SELF']); // Module Name
24 | $action_name = $_SERVER['REQUEST_METHOD']; // Action
25 | $user_ip = $_SERVER['REMOTE_ADDR']; // IP
26 |
27 | // Application Code
28 | $exectime = Timer::stop();
29 |
30 | $msg = '[START';
31 | // Info
32 | $msg_item = array();
33 | $msg_item[] = date('Y-m-d'); // Date
34 | $msg_item[] = date("Y-m-d_H:i:s"); // Date Time
35 | $msg_item[] = date("H"); // Hour
36 | $msg_item[] = date("i"); // Minute
37 | $msg_item[] = $application_name; // Application
38 | $msg_item[] = $module_name; // Module
39 | $msg_item[] = $action_name; // Action
40 | $msg_item[] = 'success'; // Status
41 | $msg_item[] = $exectime; // Executive Time
42 | $msg_item[] = \memory_get_peak_usage(true); // Memory Used
43 | $msg_item[] = $user_ip; // User IP address
44 |
45 | $msg .= implode(' ', $msg_item);
46 | $msg .= 'END]';
47 | $logger->info($msg);
48 | print Timer::resourceUsage();
49 | echo '
'.$msg;
50 | echo '
'.'Done';
51 |
--------------------------------------------------------------------------------
/php/udp_server.php:
--------------------------------------------------------------------------------
1 | on('Packet', function ($server, $data, $clientInfo) {
13 | global $config;
14 | //$server->sendto($clientInfo['address'], $clientInfo['port'], "Server : " . $data);
15 |
16 | $connection = new AMQPStreamConnection($config['host'], $config['port'], $config['user'], $config['pass']);
17 | $channel = $connection->channel();
18 | $channel->queue_declare($config['queue_sys_log'], false, true, false, false);
19 |
20 | $msg = new AMQPMessage($data, array('delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT));
21 | $channel->basic_publish($msg, '', $config['queue_sys_log']);
22 | // echo " [x] Sent '".$data."'\n";
23 |
24 | $channel->close();
25 | $connection->close();
26 | });
27 |
28 | // Start the server
29 | $server->start();
30 |
--------------------------------------------------------------------------------
/php/udp_worker.php:
--------------------------------------------------------------------------------
1 | database($config['clickhouse']['database']);
21 |
22 | $exchange = 'router';
23 | $queue = $config['rabbitmq']['queue_sys_log'];
24 | $consumerTag = 'consumer';
25 |
26 | $connection = new AMQPStreamConnection($config['host'], $config['port'], $config['user'], $config['pass']);
27 | $channel = $connection->channel();
28 | /*
29 | The following code is the same both in the consumer and the producer.
30 | In this way we are sure we always have a queue to consume from and an
31 | exchange where to publish messages.
32 | */
33 | /*
34 | name: $queue
35 | passive: false
36 | durable: true // the queue will survive server restarts
37 | exclusive: false // the queue can be accessed in other channels
38 | auto_delete: false //the queue won't be deleted once the channel is closed.
39 | */
40 | $channel->queue_declare($queue, false, true, false, false);
41 | /*
42 | name: $exchange
43 | type: direct
44 | passive: false
45 | durable: true // the exchange will survive server restarts
46 | auto_delete: false //the exchange won't be deleted once the channel is closed.
47 | */
48 | $channel->exchange_declare($exchange, AMQPExchangeType::DIRECT, false, true, false);
49 | $channel->queue_bind($queue, $exchange);
50 | /**
51 | * @param \PhpAmqpLib\Message\AMQPMessage $message
52 | */
53 | $callback = function ($message) {
54 | global $current,$limit, $db, $config, $request_logs, $php_error_logs, $syslog_logs;
55 |
56 | $msg = $message->body;
57 |
58 | $tmp = explode(' ', $msg);
59 | if ($msg[0] == "<") { // Đây là Log Apache
60 | $host = $tmp[3];
61 | } else {
62 | $host = $tmp[1]; // Đây là Log App
63 | }
64 |
65 | // detect request App log
66 | if (preg_match('/\[START(.*)?END\]/', $msg, $match)) {
67 | $parts = explode(' ', $match[1]);
68 | $request_logs[] = [
69 | $parts[0], // Date
70 | str_replace('_', ' ', $parts[1]), // Date Time
71 | (int)$parts[2], // Hour
72 | (int)$parts[3], // Minute
73 | $parts[4], // Application
74 | $parts[5], // Module
75 | $parts[6], // Action
76 | $parts[7], // Status
77 | (float)$parts[8], // Executive Time
78 | (int)$parts[9], // Memory Used
79 | $parts[10], // User IP address
80 | ];
81 | } elseif (strpos($msg, '[php') !== false) { // Detect PHP Error log
82 | // <13>Jan 12 04:52:36 Log-Server [Sun Jan 12 04:52:36.327940 2020] [php7:warn] [pid 29377] [client 14.161.12.25:64472] PHP Warning: fopen(example.csv): failed to open stream: No such file or directory in /var/www/html/test.php on line 12
83 | $log = strstr($msg, '[');
84 | $log_data = getParsedLog($log);
85 |
86 | $php_error_logs[] = [
87 | date('Y-m-d'),
88 | $log_data['dateTime'],
89 | $host,
90 | $log_data['type'],
91 | $log_data['file'],
92 | $log_data['line'],
93 | $log_data['message'],
94 | $log_data['ip_address'],
95 | ];
96 | } else { // Detect Syslog
97 | $syslog_logs[] = [
98 | date('Y-m-d'),
99 | date('Y-m-d H:i:s'),
100 | $msg,
101 | '', // TODO
102 | $host
103 | ];
104 | }
105 | $message->delivery_info['channel']->basic_ack($message->delivery_info['delivery_tag']);
106 |
107 | $current++;
108 | if ($current>=$limit) {
109 | // Start Import server log
110 | $current = 0;
111 | // detect request log
112 | if (count($request_logs)>0) {
113 | $stat = $db->insert(
114 | $config['clickhouse']['log_request_table'],
115 | $request_logs,
116 | ['lr_date', 'lr_datetime', 'lr_hour', 'lr_minute', 'lr_application', 'lr_module', 'lr_action', 'lr_status', 'lr_exectime', 'lr_memory', 'lr_ip' ]
117 | );
118 | echo "\n---=============== Start Log to ".$config['clickhouse']['log_request_table']." with ".count($request_logs)." records ==============-----\n";
119 | $request_logs = array();
120 | }
121 | if (count($php_error_logs)>0) {
122 | $stat = $db->insert(
123 | $config['clickhouse']['log_php_error_table'],
124 | $php_error_logs,
125 | ['lp_date', 'lp_datetime', 'lp_host', 'lp_type', 'lp_file', 'lp_line', 'lp_message', 'lp_ip' ]
126 | );
127 | echo "\n---=============== Start Log to ".$config['clickhouse']['log_php_error_table']." with ".count($php_error_logs)." records ==============-----\n";
128 | $php_error_logs = array();
129 | }
130 | if (count($syslog_logs)>0) {
131 | $stat = $db->insert(
132 | $config['clickhouse']['log_syslog_table'],
133 | $syslog_logs,
134 | ['ls_date', 'ls_datetime', 'ls_message', 'ls_tag', 'ls_host']
135 | );
136 | echo "\n---=============== Start Log to ".$config['clickhouse']['log_syslog_table']." with ".count($syslog_logs)." records ==============-----\n";
137 | $syslog_logs = array();
138 | }
139 | }
140 | };
141 | /*
142 | queue: Queue from where to get the messages
143 | consumer_tag: Consumer identifier
144 | no_local: Don't receive messages published by this consumer.
145 | no_ack: If set to true, automatic acknowledgement mode will be used by this consumer. See https://www.rabbitmq.com/confirms.html for details.
146 | exclusive: Request exclusive consumer access, meaning only this consumer can access the queue
147 | nowait:
148 | callback: A PHP Callback
149 | */
150 | $channel->basic_consume($queue, $consumerTag, false, false, false, false, $callback);
151 |
152 | // Loop as long as the channel has callbacks registered
153 | while ($channel->is_consuming()) {
154 | $channel->wait();
155 | }
156 |
157 | function shutdown($channel, $connection)
158 | {
159 | $channel->close();
160 | $connection->close();
161 | }
162 |
163 | register_shutdown_function('shutdown', $channel, $connection);
164 |
--------------------------------------------------------------------------------
/supervisor/http_server.conf:
--------------------------------------------------------------------------------
1 | [inet_http_server]
2 | port=9001
3 | username = admin
4 | password = XpasswordX
--------------------------------------------------------------------------------
/supervisor/udp_server.conf:
--------------------------------------------------------------------------------
1 | [program:udp_server]
2 | command=php /var/www/udp_server.php
3 | numprocs=1
4 | directory=/tmp
5 | autostart=true
6 | autorestart=true
7 | startsecs=5
8 | startretries=10
9 | redirect_stderr=false
10 | stdout_logfile=/var/www/logs/udp_server.out.log
11 | stdout_capture_maxbytes=1MB
12 | stderr_logfile=/var/www/logs/udd_server.error.log
13 | stderr_capture_maxbytes=1MB
--------------------------------------------------------------------------------
/supervisor/udp_worker.conf:
--------------------------------------------------------------------------------
1 | [program:udp_worker]
2 | command=php /var/www/udp_worker.php
3 | numprocs=1
4 | directory=/tmp
5 | autostart=true
6 | autorestart=true
7 | startsecs=5
8 | startretries=10
9 | redirect_stderr=false
10 | stdout_logfile=/var/www/logs/udp_worker.out.log
11 | stdout_capture_maxbytes=1MB
12 | stderr_logfile=/var/www/logs/udp_worker.error.log
13 | stderr_capture_maxbytes=1MB
--------------------------------------------------------------------------------
/syslog-ng/remote_host.conf:
--------------------------------------------------------------------------------
1 |
2 | template SelfFormat {
3 | template("<${PRI}>1 ${ISODATE} ${HOST} ${PROGRAM} ${PID} ${MSGID} $MSG\n");
4 | template_escape(no);
5 | };
6 |
7 | #Define a new source that essentially 'tails' the apache logs
8 | source s_apache2 {
9 | # file("/var/log/apache2/access.log" flags(no-parse));
10 | file("/var/log/apache2/error.log" flags(no-parse));
11 | file("/var/www/log/app.log" flags(no-parse));
12 | };
13 |
14 | #Send the logs off to a remote logging server (if required)
15 | destination loghost {
16 | udp("IP_UDP_LOG_SERVER" port(9502) template(SelfFormat));
17 | };
18 | log {
19 | source(s_apache2);
20 | destination(loghost);
21 | };
--------------------------------------------------------------------------------