├── 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 | ![Cấu trúc hệ thống Log](/assets/images/structure.png "Cấu trúc hệ thống Log") 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 | ![RabbitMQ](/assets/images/rabbitMQ.png "RabbitMQ") 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 | ![Supervisor](/assets/images/supervisor.png "Supervisor") 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 | ![Grafana](/assets/images/grafana.png "Grafana") 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 | }; --------------------------------------------------------------------------------