自由开源,超高性能,免费的java分析nginx网站日志的工具,最终生成方便查看的网页版的网站监控报表。
自己用java分析nginx网站日志
先看效果图:
一,为什么不用第三方的网站日志分析工具
大家都熟悉的第三方的nginx网站日志分析工具,有收费的宝塔网站监控面板,有免费的goaccess和基于js的51la和百度网站统计等等。那为什么还要自己做一个网站日志分析工具呢?
- 第三方网站日志分析工具要么收费,要么不好用;
- 咱们自己做,可以根据自己的需要,对感兴趣的数据进行统计分析;
- 可以自己设计外观,自己喜欢什么风格就做成什么风格;
- 自己做不会泄露网站资料;
二,网站日志分析的基本原理
咱们nginx服务器生成的原始网站日志文件是一个文本文件,而且是非常有规律的格式化的文本文件,这样就便于我们对这个原始的网站日志文件进行处理。
第一步,就是生成结构化的数据:把原始日志文件的各个数据元素提取出来,放进sqlite数据库临时的网站统计表中。
第二步,在sqlite数据库的网站统计表中通过各种查询方法,统计出我们感兴趣的数据,把统计出的数据再存储到的sqlite数据库中的日志存档表中。
第三部,查询日志存档表,将查询结果以html网页的形式输出到网站根目录下,我们可以直接在浏览器中访问和查看。
三,下面介绍具体的算法和源代码
(一),创建sqlite数据库
package com.face1688;
import java.io.File;
import java.sql.*;
public class SHelper {
public static String dbFilePath = "site_record.db";
public static Connection cn;
public static ResultSet rs;
public static void connect() {
try {
File file = new File(dbFilePath);
boolean exist = file.exists();
if (!exist) {
file.createNewFile();
}
Class.forName("org.sqlite.JDBC");
cn = DriverManager.getConnection("jdbc:sqlite:" + dbFilePath);
if (!exist)
createTables();
else
readSettings();
} catch (Exception e) {
Log.error(e);
}
}
/**
* 数据库字段不能以数字开头。
*/
public static void createTables() {
try {
//删除表和索引
Statement ps = cn.createStatement();
ps.executeUpdate("drop index if exists index_wztjb");
ps.executeUpdate("drop table if exists wztjb");
ps.executeUpdate("drop index if exists index_pzb");
ps.executeUpdate("drop table if exists pzb");
ps.executeUpdate("drop index if exists index_rzcdb");
ps.executeUpdate("drop table if exists rzcdb");
//新建表
//配置表
ps.executeUpdate("Create table pzb(name text primary key,myvalue blob)");
//网站统计表
ps.executeUpdate("Create table if not exists wztjb(id integer primary key autoincrement,date,time,ip,request,ret_code,ret_count,enter,client)");
//索引
ps.executeUpdate("Create index if not exists index_wztjb on wztjb(id,date,ip,ret_code,ret_count,enter,client)");
//日志存档表
ps.executeUpdate("Create table if not exists rzcdb(id integer primary key autoincrement,date,pv,uv,xzl,gjs,baiduspider,smspider,qihuspider,bingbot,googlebot)");
//索引
ps.executeUpdate("Create index if not exists index_rzcdb on rzcdb(id,date)");
} catch (Exception e) {
Log.error(e);
}
}
/**
* 复位网站统计表,删除后重建
*/
public static void resetWztjb() {
try {
//删除表和索引
Statement ps = cn.createStatement();
ps.executeUpdate("drop table if exists wztjb");
ps.executeUpdate("drop index if exists index_wztjb");
//网站统计表
ps.executeUpdate("Create table if not exists wztjb(id integer primary key autoincrement,date,time,ip,request,ret_code,ret_count,enter,client)");
//索引
ps.executeUpdate("Create index if not exists index_wztjb on wztjb(id,date,ip,ret_code,ret_count,enter,client)");
} catch (Exception e) {
Log.error(e);
}
}
//region 读取配置
public static void readSettings() {
try {
PreparedStatement ps = cn.prepareStatement("select name,myvalue from pzb");
rs = ps.executeQuery();
String name;
while (rs.next()) {
name = rs.getString(1);
byte[] b = rs.getBytes(2);
switch (name) {
case "lastRow"://最新统计到的行号
ServerSettings.lastRow = BitConverter.toInt(b, 0);
break;
case "minutes"://间隔分钟
ServerSettings.minutes = BitConverter.toInt(b, 0);
if(ServerSettings.minutes ==0)ServerSettings.minutes = 5;
break;
default:
break;
}
}
rs.close();
} catch (Exception e) {
Log.error(e);
}
//加载默认设置
}
private static void UpdateSettingsByName(String name, byte[] value) {
try {
PreparedStatement ps = cn.prepareStatement("insert or replace into pzb values(?,?)");
ps.setString(1, name);
ps.setBytes(2, value);
ps.executeUpdate();
} catch (Exception e) {
Log.info(e.getMessage());
}
}
private static void Write(String name, byte[] value) {
UpdateSettingsByName(name, value);
}
public static void SaveSettingsByName(String name, String value) {
byte[] bv = value.getBytes();
Write(name, bv);
}
public static void SaveSettingsByName(String name, int value) {
byte[] bv = BitConverter.getBytes(value);
Write(name, bv);
}
}
(二),核心分析代码
package com.face1688;
import java.io.*;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.*;
public class Main {
public static boolean appRun = true;
public static int startTime;
public static boolean busy = false;
static PreparedStatement psInsert;
public static void main(String[] args) throws Exception {
SHelper.connect();
psInsert = SHelper.cn.prepareStatement("insert into wztjb(date,time,ip,request,ret_code,ret_count,enter,client) values(?,?,?,?,?,?,?,?)");
startTime = ServerTime.getShortTime();
Log.info("日志分析服务正在运行");
new Thread(new Runnable() {
public void run() {
while (appRun) {
try {
busy = true;
SHelper.cn.setAutoCommit(false);
readFromLine(ServerSettings.lastRow);
saveSiteLog();
updateHtmlReport();
SHelper.cn.commit();
Log.info("更新完成");
busy = false;
Thread.sleep(1000 * ServerSettings.minutes*60);//更新频率,10分钟,随便设置
//Thread.sleep(1000 * 10);//更新频率,10分钟,随便设置
} catch (Exception e) {
Log.info(e.getMessage());
busy = false;
break;
}
}
}
}).start();
Controller.run();//显示控制台
}
/**
* 从指定的行开始读取到结束,并且保持最后的行号。
*
* @param row 行号
*/