mirror of https://gitee.com/stylefeng/roses
【7.6.0】【框架改造】删掉文件存储的业务日志
parent
674502dfdf
commit
d12ac2be31
|
@ -57,7 +57,6 @@
|
|||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
/*
|
||||
* Copyright [2020-2030] [https://www.stylefeng.cn]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* Guns采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
|
||||
*
|
||||
* 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
* 2.请不要删除和修改Guns源码头部的版权声明。
|
||||
* 3.请保留源码和相关描述文件的项目出处,作者声明等。
|
||||
* 4.分发源码时候,请注明软件出处 https://gitee.com/stylefeng/guns
|
||||
* 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/stylefeng/guns
|
||||
* 6.若您的项目无法满足以上几点,可申请商业授权
|
||||
*/
|
||||
package cn.stylefeng.roses.kernel.log.api.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 日志存储的方式,数据库还是文件
|
||||
*
|
||||
* @author fengshuonan
|
||||
* @since 2020/12/24 14:08
|
||||
*/
|
||||
@Getter
|
||||
public enum LogSaveTypeEnum {
|
||||
|
||||
/**
|
||||
* 存储到数据库
|
||||
*/
|
||||
DB("db"),
|
||||
|
||||
/**
|
||||
* 存储到文件
|
||||
*/
|
||||
FILE("file");
|
||||
|
||||
LogSaveTypeEnum(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
private final String code;
|
||||
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
/*
|
||||
* Copyright [2020-2030] [https://www.stylefeng.cn]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* Guns采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
|
||||
*
|
||||
* 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
* 2.请不要删除和修改Guns源码头部的版权声明。
|
||||
* 3.请保留源码和相关描述文件的项目出处,作者声明等。
|
||||
* 4.分发源码时候,请注明软件出处 https://gitee.com/stylefeng/guns
|
||||
* 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/stylefeng/guns
|
||||
* 6.若您的项目无法满足以上几点,可申请商业授权
|
||||
*/
|
||||
package cn.stylefeng.roses.kernel.log.api.pojo.log;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 日志配置信息
|
||||
*
|
||||
* @author liuhanqing
|
||||
* @since 2020-12-20 13:53
|
||||
**/
|
||||
@Data
|
||||
public class SysLogProperties {
|
||||
|
||||
/**
|
||||
* 日志存储类型:db-数据库,file-文件,默认存储在数据库中
|
||||
*/
|
||||
private String type = "db";
|
||||
|
||||
/**
|
||||
* file存储类型日志文件的存储位置
|
||||
*/
|
||||
private String fileSavePath = "_sys_log";
|
||||
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
日志记录的sdk,用于将日志记录到文件中,提供相关日志管理接口
|
|
@ -1,45 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>cn.stylefeng.roses</groupId>
|
||||
<artifactId>kernel-d-log</artifactId>
|
||||
<version>7.6.0</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>log-sdk-file</artifactId>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!--日志模块的api-->
|
||||
<dependency>
|
||||
<groupId>cn.stylefeng.roses</groupId>
|
||||
<artifactId>log-api</artifactId>
|
||||
<version>${roses.version}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!--登陆模块的api-->
|
||||
<dependency>
|
||||
<groupId>cn.stylefeng.roses</groupId>
|
||||
<artifactId>auth-api</artifactId>
|
||||
<version>${roses.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!--config模块的api-->
|
||||
<!--用来配置一些参数,例如日志的存放路径等-->
|
||||
<dependency>
|
||||
<groupId>cn.stylefeng.roses</groupId>
|
||||
<artifactId>config-api</artifactId>
|
||||
<version>${roses.version}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -1,388 +0,0 @@
|
|||
/*
|
||||
* Copyright [2020-2030] [https://www.stylefeng.cn]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* Guns采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
|
||||
*
|
||||
* 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
* 2.请不要删除和修改Guns源码头部的版权声明。
|
||||
* 3.请保留源码和相关描述文件的项目出处,作者声明等。
|
||||
* 4.分发源码时候,请注明软件出处 https://gitee.com/stylefeng/guns
|
||||
* 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/stylefeng/guns
|
||||
* 6.若您的项目无法满足以上几点,可申请商业授权
|
||||
*/
|
||||
package cn.stylefeng.roses.kernel.log.file;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.lang.Dict;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.stylefeng.roses.kernel.auth.api.context.LoginContext;
|
||||
import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult;
|
||||
import cn.stylefeng.roses.kernel.log.api.LogManagerApi;
|
||||
import cn.stylefeng.roses.kernel.log.api.exception.LogException;
|
||||
import cn.stylefeng.roses.kernel.log.api.exception.enums.LogExceptionEnum;
|
||||
import cn.stylefeng.roses.kernel.log.api.pojo.manage.LogManagerRequest;
|
||||
import cn.stylefeng.roses.kernel.log.api.pojo.record.LogRecordDTO;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.*;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.stylefeng.roses.kernel.log.api.constants.LogConstants.DEFAULT_BEGIN_PAGE_NO;
|
||||
import static cn.stylefeng.roses.kernel.log.api.constants.LogConstants.DEFAULT_PAGE_SIZE;
|
||||
import static cn.stylefeng.roses.kernel.log.api.constants.LogFileConstants.FILE_CONTRACT_SYMBOL;
|
||||
import static cn.stylefeng.roses.kernel.log.api.constants.LogFileConstants.FILE_SUFFIX;
|
||||
|
||||
/**
|
||||
* 文件日志读取管理实现类
|
||||
*
|
||||
* @author majianguo
|
||||
* @since 2020/11/3 上午10:56
|
||||
*/
|
||||
@Slf4j
|
||||
public class FileLogManagerServiceImpl implements LogManagerApi {
|
||||
|
||||
private final String fileSavePath;
|
||||
|
||||
private Integer total;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param fileSavePath 文件保存路径
|
||||
*/
|
||||
public FileLogManagerServiceImpl(String fileSavePath) {
|
||||
this.fileSavePath = fileSavePath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LogRecordDTO> findList(LogManagerRequest logManagerParam) {
|
||||
PageResult<LogRecordDTO> pageResult = findPage(logManagerParam);
|
||||
return pageResult.getRows();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<LogRecordDTO> findPage(LogManagerRequest logManagerParam) {
|
||||
|
||||
// 文件日志,必须有AppName,否则文件太多太大
|
||||
if (ObjectUtil.isEmpty(logManagerParam.getAppName())) {
|
||||
throw new LogException(LogExceptionEnum.APP_NAME_NOT_EXIST);
|
||||
}
|
||||
|
||||
// 文件日志,必须有开始时间,否则文件太多太大
|
||||
if (ObjectUtil.isEmpty(logManagerParam.getBeginDate())) {
|
||||
throw new LogException(LogExceptionEnum.BEGIN_DATETIME_NOT_EXIST);
|
||||
}
|
||||
|
||||
// 获取文件路径
|
||||
String filePath = getLogPath(logManagerParam.getAppName(), logManagerParam.getBeginDate());
|
||||
|
||||
// 文件当前指针
|
||||
long filePointer = 0L;
|
||||
if (logManagerParam.getPageNo() == null) {
|
||||
logManagerParam.setPageNo(DEFAULT_BEGIN_PAGE_NO);
|
||||
} else {
|
||||
// 如果页数不等于1,则根据当前登陆用户信息取出上次读取文件的位置
|
||||
if (!DEFAULT_BEGIN_PAGE_NO.equals(logManagerParam.getPageNo())) {
|
||||
Object pointer = LoginContext.me().getLoginUser().getOtherInfos().get("filePointer");
|
||||
if (ObjectUtil.isNotEmpty(pointer)) {
|
||||
filePointer = (long) pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (logManagerParam.getPageSize() == null) {
|
||||
logManagerParam.setPageSize(DEFAULT_PAGE_SIZE);
|
||||
}
|
||||
// 返回分页结果
|
||||
PageResult<LogRecordDTO> pageResult = new PageResult<>();
|
||||
pageResult.setPageSize(logManagerParam.getPageSize());
|
||||
|
||||
// 读取日志
|
||||
List<LogRecordDTO> dtos = readLog(filePath, filePointer, logManagerParam.getPageSize());
|
||||
pageResult.setRows(dtos);
|
||||
pageResult.setTotalRows(total);
|
||||
return pageResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void del(LogManagerRequest logManagerParam) {
|
||||
|
||||
// 删除操作,必须有appName
|
||||
if (ObjectUtil.isEmpty(logManagerParam.getAppName())) {
|
||||
throw new LogException(LogExceptionEnum.APP_NAME_NOT_EXIST);
|
||||
}
|
||||
|
||||
// 删除操作,必须有appName
|
||||
if (ObjectUtil.isEmpty(logManagerParam.getBeginDate())) {
|
||||
throw new LogException(LogExceptionEnum.BEGIN_DATETIME_NOT_EXIST);
|
||||
}
|
||||
|
||||
// 文件日志,必须有结束时间,否则文件太多太大
|
||||
if (ObjectUtil.isEmpty(logManagerParam.getEndDate())) {
|
||||
throw new LogException(LogExceptionEnum.END_DATETIME_NOT_EXIST);
|
||||
}
|
||||
|
||||
// 计算开始和结束两个时间之间的所有日期
|
||||
List<String> dates = getIntervalDate(logManagerParam.getBeginDate(), logManagerParam.getEndDate());
|
||||
|
||||
// 查找每一天的日志
|
||||
for (String date : dates) {
|
||||
|
||||
// 拼接文件名称
|
||||
String logPath = getLogPath(logManagerParam.getAppName(), date);
|
||||
|
||||
// 删除日志
|
||||
if (FileUtil.exist(logPath)) {
|
||||
FileUtil.del(logPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public LogRecordDTO detail(LogManagerRequest logManagerRequest) {
|
||||
|
||||
// 文件日志,必须有AppName,否则文件太多太大
|
||||
if (ObjectUtil.isEmpty(logManagerRequest.getAppName())) {
|
||||
throw new LogException(LogExceptionEnum.APP_NAME_NOT_EXIST);
|
||||
}
|
||||
|
||||
// 文件日志,必须有开始时间,否则文件太多太大
|
||||
if (ObjectUtil.isEmpty(logManagerRequest.getBeginDate())) {
|
||||
throw new LogException(LogExceptionEnum.BEGIN_DATETIME_NOT_EXIST);
|
||||
}
|
||||
|
||||
// 获取文件路径
|
||||
String filePath = getLogPath(logManagerRequest.getAppName(), logManagerRequest.getBeginDate());
|
||||
return this.readLog(filePath, logManagerRequest.getLogId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据app名称和日期获取日志文件全路径
|
||||
*
|
||||
* @param appName APP名称
|
||||
* @param date 那一天的日志
|
||||
* @return 文件全路径名称
|
||||
* @author majianguo
|
||||
* @since 2020/11/3 下午4:29
|
||||
*/
|
||||
private String getLogPath(String appName, String date) {
|
||||
|
||||
// 根据传入的AppName和时间来定位确定一个唯一的文件名称
|
||||
String fileName = appName + FILE_CONTRACT_SYMBOL + DateUtil.parse(date).toDateStr() + FILE_SUFFIX;
|
||||
|
||||
// 文件绝对路径生成,带文件名的完整路径
|
||||
String fileAbsolutePath = fileSavePath + File.separator;
|
||||
return fileAbsolutePath + fileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id获取日志记录
|
||||
*
|
||||
* @author chenjinlong
|
||||
* @since 2021/2/1 19:54
|
||||
*/
|
||||
private LogRecordDTO readLog(String path, Long logId) {
|
||||
|
||||
// 判断文件是否存在,不存在直接返回Null
|
||||
if (!FileUtil.exist(path)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
LogRecordDTO logRecordDTO = new LogRecordDTO();
|
||||
RandomAccessFile file;
|
||||
try {
|
||||
// 创建随机读文件流
|
||||
file = new RandomAccessFile(path, "r");
|
||||
|
||||
while (true) {
|
||||
|
||||
String str = file.readLine();
|
||||
|
||||
// 读取到的字符串不为空
|
||||
if (ObjectUtil.isNotEmpty(str)) {
|
||||
logRecordDTO = parseObject(str);
|
||||
if (logRecordDTO.getLogId().equals(logId)) {
|
||||
return logRecordDTO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
if (log.isDebugEnabled()) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
log.error(e.getMessage());
|
||||
}
|
||||
return logRecordDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从指定的文件指针处,开始读取指定行数的数据
|
||||
*
|
||||
* @param path 文件全路径
|
||||
* @param filePointer 开始读取文件指针位置
|
||||
* @param lineNum 读取行数
|
||||
* @author majianguo
|
||||
* @since 2020/11/3 下午1:36
|
||||
*/
|
||||
private List<LogRecordDTO> readLog(String path, long filePointer, int lineNum) {
|
||||
// 判断文件是否存在,不存在直接返回Null
|
||||
if (!FileUtil.exist(path)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 返回结果集
|
||||
List<LogRecordDTO> readLines = new ArrayList<>();
|
||||
|
||||
RandomAccessFile file;
|
||||
try {
|
||||
// 创建随机读文件流
|
||||
file = new RandomAccessFile(path, "r");
|
||||
|
||||
// 设置文件指针
|
||||
file.seek(filePointer);
|
||||
|
||||
// 从设置的指针处开始读取文件
|
||||
for (int i = 0; i < lineNum; ++i) {
|
||||
String str = file.readLine();
|
||||
|
||||
// 读取到的字符串不为空
|
||||
if (ObjectUtil.isNotEmpty(str)) {
|
||||
LogRecordDTO recordDTO = parseObject(str);
|
||||
|
||||
// 格式转换没有问题就加入到列表中
|
||||
if (ObjectUtil.isNotEmpty(recordDTO)) {
|
||||
readLines.add(recordDTO);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//获取总行数
|
||||
|
||||
total = getTotalLines(new File(path));
|
||||
|
||||
// 在用户信息中记录当前用户读取的文件指针
|
||||
LoginContext.me().getLoginUser().setOtherInfos(Dict.create().set("filePointer", file.getFilePointer()));
|
||||
|
||||
} catch (Exception e) {
|
||||
if (log.isDebugEnabled()) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
log.error(e.getMessage());
|
||||
}
|
||||
return readLines;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据传入的JSON字符串转换成JAVA对象
|
||||
*
|
||||
* @param jsonStr json字符串
|
||||
* @return 返回格式化后的Java对象 如果格式错误,则返回Null
|
||||
* @author majianguo
|
||||
* @since 2020/11/3 下午2:16
|
||||
*/
|
||||
private LogRecordDTO parseObject(String jsonStr) {
|
||||
LogRecordDTO logRecordDTO = null;
|
||||
try {
|
||||
|
||||
// 这里接受到的jsonStr是乱码的,需要通过getBytes方法转换成字节数组,然后通过newString的方式再转换回来
|
||||
logRecordDTO = JSON.parseObject(new String(getBytes(jsonStr.toCharArray())), LogRecordDTO.class);
|
||||
} catch (Exception e) {
|
||||
if (log.isDebugEnabled()) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
log.error(e.getMessage());
|
||||
}
|
||||
return logRecordDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解决readLine读取中文乱码问题
|
||||
*
|
||||
* @param chars 乱码的字符串转数组
|
||||
* @author majianguo
|
||||
* @since 2020/11/3 下午4:03
|
||||
*/
|
||||
public static byte[] getBytes(char[] chars) {
|
||||
byte[] result = new byte[chars.length];
|
||||
for (int i = 0; i < chars.length; i++) {
|
||||
result[i] = (byte) chars[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算两个日期之间所有的日期
|
||||
*
|
||||
* @param start 开始日期 间隔
|
||||
* @param end 结束日期
|
||||
* @author majianguo
|
||||
* @since 2020/11/3 下午4:23
|
||||
*/
|
||||
public static List<String> getIntervalDate(String start, String end) {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
||||
|
||||
// 保存日期集合
|
||||
List<String> list = new ArrayList<>();
|
||||
try {
|
||||
|
||||
Date dateStart = sdf.parse(start);
|
||||
Date dateEnd = sdf.parse(end);
|
||||
Date date = dateStart;
|
||||
|
||||
// 用Calendar 进行日期比较判断
|
||||
Calendar cd = Calendar.getInstance();
|
||||
while (date.getTime() <= dateEnd.getTime()) {
|
||||
list.add(sdf.format(date));
|
||||
cd.setTime(date);
|
||||
|
||||
// 增加一天 放入集合
|
||||
cd.add(Calendar.DATE, 1);
|
||||
date = cd.getTime();
|
||||
}
|
||||
|
||||
} catch (ParseException e) {
|
||||
if (log.isDebugEnabled()) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
log.error(e.getMessage());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取总行数
|
||||
*
|
||||
* @param file 日志文件路径
|
||||
* @return 日志总行数
|
||||
* @author chenjinlong
|
||||
* @since 2021/2/1 19:45
|
||||
*/
|
||||
public int getTotalLines(File file) throws IOException {
|
||||
FileReader in = new FileReader(file);
|
||||
LineNumberReader reader = new LineNumberReader(in);
|
||||
reader.skip(Long.MAX_VALUE);
|
||||
int lines = reader.getLineNumber();
|
||||
reader.close();
|
||||
return lines;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,351 +0,0 @@
|
|||
/*
|
||||
* Copyright [2020-2030] [https://www.stylefeng.cn]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* Guns采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
|
||||
*
|
||||
* 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
* 2.请不要删除和修改Guns源码头部的版权声明。
|
||||
* 3.请保留源码和相关描述文件的项目出处,作者声明等。
|
||||
* 4.分发源码时候,请注明软件出处 https://gitee.com/stylefeng/guns
|
||||
* 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/stylefeng/guns
|
||||
* 6.若您的项目无法满足以上几点,可申请商业授权
|
||||
*/
|
||||
package cn.stylefeng.roses.kernel.log.file;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.log.Log;
|
||||
import cn.hutool.log.LogFactory;
|
||||
import cn.stylefeng.roses.kernel.log.api.LogRecordApi;
|
||||
import cn.stylefeng.roses.kernel.log.api.constants.LogFileConstants;
|
||||
import cn.stylefeng.roses.kernel.log.api.pojo.record.LogRecordDTO;
|
||||
import cn.stylefeng.roses.kernel.log.api.threadpool.LogManagerThreadPool;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import static cn.stylefeng.roses.kernel.log.api.constants.LogFileConstants.FILE_CONTRACT_SYMBOL;
|
||||
import static cn.stylefeng.roses.kernel.log.api.constants.LogFileConstants.FILE_SUFFIX;
|
||||
|
||||
/**
|
||||
* 文件存储方式的日志记录器
|
||||
*
|
||||
* @author fengshuonan
|
||||
* @since 2020/10/28 14:52
|
||||
*/
|
||||
public class FileLogRecordServiceImpl implements LogRecordApi {
|
||||
|
||||
private final LogManagerThreadPool logManagerThreadPool;
|
||||
|
||||
private final LogRefreshManager logRefreshManager;
|
||||
|
||||
private final String fileSavePath;
|
||||
|
||||
public FileLogRecordServiceImpl(String fileSavePath, LogManagerThreadPool logManagerThreadPool) {
|
||||
this.fileSavePath = fileSavePath;
|
||||
this.logManagerThreadPool = logManagerThreadPool;
|
||||
this.logRefreshManager = new LogRefreshManager();
|
||||
this.logRefreshManager.start();
|
||||
}
|
||||
|
||||
public FileLogRecordServiceImpl(String fileSavePath, LogManagerThreadPool logManagerThreadPool, long sleepTime, int maxCount) {
|
||||
this.fileSavePath = fileSavePath;
|
||||
this.logManagerThreadPool = logManagerThreadPool;
|
||||
this.logRefreshManager = new LogRefreshManager(sleepTime, maxCount);
|
||||
this.logRefreshManager.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 日志文件的组成形式应为appName-年-月-日.log
|
||||
*
|
||||
* @author fengshuonan
|
||||
* @since 2020/10/28 15:53
|
||||
*/
|
||||
@Override
|
||||
public void add(LogRecordDTO logRecordDTO) {
|
||||
|
||||
if (logRecordDTO == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 输出日志
|
||||
addBatch(CollectionUtil.list(false, logRecordDTO));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量输出日志
|
||||
*
|
||||
* @param list 待输出日志列表
|
||||
* @author majianguo
|
||||
* @since 2020/11/2 下午2:59
|
||||
*/
|
||||
@Override
|
||||
public void addBatch(List<LogRecordDTO> list) {
|
||||
|
||||
if (ObjectUtil.isEmpty(list)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取appName
|
||||
String appName = list.get(0).getAppName();
|
||||
if (StrUtil.isBlank(appName)) {
|
||||
appName = LogFileConstants.DEFAULT_LOG_FILE_NAME;
|
||||
}
|
||||
|
||||
// 获取日志记录的日期
|
||||
Date dateTime = list.get(0).getDateTime();
|
||||
if (dateTime == null) {
|
||||
dateTime = new Date();
|
||||
}
|
||||
String dateStr = DateUtil.formatDate(dateTime);
|
||||
|
||||
// 拼接文件名
|
||||
String fileName = appName + FILE_CONTRACT_SYMBOL + dateStr + FILE_SUFFIX;
|
||||
|
||||
// 文件绝对路径生成,带文件名的完整路径
|
||||
String fileAbsolutePath = fileSavePath + File.separator + fileName;
|
||||
|
||||
// 判断文件是否存在,不存在则创建
|
||||
boolean existFlag = FileUtil.exist(fileAbsolutePath);
|
||||
if (!existFlag) {
|
||||
FileUtil.touch(fileAbsolutePath);
|
||||
}
|
||||
|
||||
// 将对象转换成JSON输出
|
||||
List<String> outList = new ArrayList<>();
|
||||
for (LogRecordDTO recordDTO : list) {
|
||||
outList.add(JSON.toJSONString(recordDTO));
|
||||
}
|
||||
|
||||
// 追加日志内容
|
||||
FileUtil.appendLines(outList, fileAbsolutePath, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAsync(LogRecordDTO logRecordDTO) {
|
||||
logManagerThreadPool.executeLog(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
logRefreshManager.putLog(logRecordDTO);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 日志刷新管理器
|
||||
* <p>
|
||||
* 该类暂存所有将要写出到磁盘的日志,使用内存缓冲区减少对磁盘IO的操作
|
||||
* <p>
|
||||
* 该类维护一个最大日志数和一个刷新日志间隔,满足任意一个条件即可触发从内存写出日志到磁盘的操作
|
||||
*
|
||||
* @author majianguo
|
||||
* @since 2020/10/31 15:05
|
||||
*/
|
||||
class LogRefreshManager extends Thread {
|
||||
|
||||
/**
|
||||
* Hutool日志对象
|
||||
*/
|
||||
private final Log log = LogFactory.get();
|
||||
|
||||
/**
|
||||
* 刷新日志间隔(默认3秒),单位毫秒
|
||||
*/
|
||||
private final long sleepTime;
|
||||
|
||||
/**
|
||||
* 满足多少条就强制刷新一次(默认300条),该值只是一个大约值,实际记录并不会一定等于该值(可能会大于该值,不可能小于该值)
|
||||
*/
|
||||
private final int maxCount;
|
||||
|
||||
/**
|
||||
* 刷新数据时间标志,每次刷新都记录当前的时间戳,方便定时刷新准确判断上次刷新和本次刷新的时间间隔
|
||||
*/
|
||||
private final AtomicLong refreshMark = new AtomicLong();
|
||||
|
||||
public LogRefreshManager() {
|
||||
this.sleepTime = 3000;
|
||||
this.maxCount = 300;
|
||||
}
|
||||
|
||||
public LogRefreshManager(long sleepTime) {
|
||||
this.sleepTime = sleepTime;
|
||||
this.maxCount = 300;
|
||||
}
|
||||
|
||||
public LogRefreshManager(int maxCount) {
|
||||
this.sleepTime = 3000;
|
||||
this.maxCount = maxCount;
|
||||
}
|
||||
|
||||
public LogRefreshManager(long sleepTime, int maxCount) {
|
||||
this.sleepTime = sleepTime;
|
||||
this.maxCount = maxCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 未处理日志的消息队列
|
||||
*/
|
||||
private final Queue<LogRecordDTO> queue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
/**
|
||||
* 消息总数,队列的size方法会遍历一遍队列,所以自己维护大小
|
||||
*/
|
||||
public AtomicInteger count = new AtomicInteger(0);
|
||||
|
||||
/**
|
||||
* 往队列内新增一条日志数据
|
||||
*
|
||||
* @param logRecordDTO 日志对象
|
||||
* @author majianguo
|
||||
* @since 2020/10/31 14:59
|
||||
*/
|
||||
public void putLog(LogRecordDTO logRecordDTO) {
|
||||
|
||||
int queueDataCount = count.get();
|
||||
|
||||
// 如果是第一条消息,刷新一次refreshMark
|
||||
if (queueDataCount == 0) {
|
||||
refreshMark.getAndSet(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
// 如果后续写入磁盘的操作有异常(磁盘满了),为了防止日志刷不出去导致OOM,内存中最大只保留maxCount数一倍的日志,后续日志将丢弃
|
||||
if (queueDataCount >= (maxCount * 2)) {
|
||||
return;
|
||||
}
|
||||
|
||||
queue.offer(logRecordDTO);
|
||||
count.incrementAndGet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新日志到磁盘的操作
|
||||
*
|
||||
* @author majianguo
|
||||
* @since 2020/10/31 15:48
|
||||
*/
|
||||
private void refresh() {
|
||||
// 让睡眠线程本次不要再调本方法,睡眠至下次看refreshMark的值再决定要不要调用本方法
|
||||
refreshMark.getAndSet(System.currentTimeMillis());
|
||||
|
||||
// 获取总数
|
||||
int num = count.getAndSet(0);
|
||||
|
||||
// 缓冲队列中所有的数据
|
||||
List<LogRecordDTO> cacheAll = new ArrayList<>(num);
|
||||
|
||||
try {
|
||||
// 在队列中读取num条数据,放入list
|
||||
for (int i = 0; i < num; i++) {
|
||||
LogRecordDTO item = queue.poll();
|
||||
if (null == item) {
|
||||
break;
|
||||
}
|
||||
cacheAll.add(item);
|
||||
}
|
||||
|
||||
// 调用方法刷新到磁盘
|
||||
addBatch(cacheAll);
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
// 有异常把日志刷回队列,不要丢掉(这里可能会导致日志顺序错乱)
|
||||
for (LogRecordDTO recordDTO : cacheAll) {
|
||||
queue.offer(recordDTO);
|
||||
}
|
||||
|
||||
// 打印日志
|
||||
if (log.isDebugEnabled()) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
log.error(e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 日志数据定时执行器
|
||||
* <p>
|
||||
* 用于定时检测日志数据是否可以写入数据
|
||||
*
|
||||
* @author majianguo
|
||||
* @since 2020/10/31 15:57
|
||||
*/
|
||||
private void timing() {
|
||||
try {
|
||||
// 如果是激活状态,且消息数大于零,且符合上次调用refresh方法到目前时间的间隔,那就调用一次refresh方法
|
||||
if ((refreshMark.get() + sleepTime) <= System.currentTimeMillis() && count.get() > 0) {
|
||||
refresh();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (log.isDebugEnabled()) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
log.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 日志数据监听器
|
||||
* <p>
|
||||
* 用于监听日志消息队列,达到设定的数就开始执行刷入硬盘的操作
|
||||
*
|
||||
* @author majianguo
|
||||
* @since 2020/11/2 9:32
|
||||
*/
|
||||
private void listener() {
|
||||
try {
|
||||
// 判断如果队列里面的数据大于等于设定的最大消息数,就调用refresh方法刷新一次数据
|
||||
if (count.get() >= maxCount) {
|
||||
refresh();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (log.isDebugEnabled()) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
log.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("InfiniteLoopStatement")
|
||||
public void run() {
|
||||
try {
|
||||
for (; ; ) {
|
||||
// 消息监听器
|
||||
listener();
|
||||
// 定时任务监听器
|
||||
timing();
|
||||
TimeUnit.MILLISECONDS.sleep(10);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
if (log.isDebugEnabled()) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
log.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -37,11 +37,6 @@
|
|||
<artifactId>log-sdk-db</artifactId>
|
||||
<version>${roses.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.stylefeng.roses</groupId>
|
||||
<artifactId>log-sdk-file</artifactId>
|
||||
<version>${roses.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!--登录日志的业务-->
|
||||
<dependency>
|
||||
|
|
|
@ -24,24 +24,15 @@
|
|||
*/
|
||||
package cn.stylefeng.roses.kernel.log.starter;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.system.SystemUtil;
|
||||
import cn.stylefeng.roses.kernel.log.api.LogManagerApi;
|
||||
import cn.stylefeng.roses.kernel.log.api.LogRecordApi;
|
||||
import cn.stylefeng.roses.kernel.log.api.enums.LogSaveTypeEnum;
|
||||
import cn.stylefeng.roses.kernel.log.api.expander.LogConfigExpander;
|
||||
import cn.stylefeng.roses.kernel.log.api.pojo.log.SysLogProperties;
|
||||
import cn.stylefeng.roses.kernel.log.api.threadpool.LogManagerThreadPool;
|
||||
import cn.stylefeng.roses.kernel.log.db.DbLogManagerServiceImpl;
|
||||
import cn.stylefeng.roses.kernel.log.db.DbLogRecordServiceImpl;
|
||||
import cn.stylefeng.roses.kernel.log.db.service.SysLogService;
|
||||
import cn.stylefeng.roses.kernel.log.db.service.impl.SysLogServiceImpl;
|
||||
import cn.stylefeng.roses.kernel.log.file.FileLogManagerServiceImpl;
|
||||
import cn.stylefeng.roses.kernel.log.file.FileLogRecordServiceImpl;
|
||||
import cn.stylefeng.roses.kernel.log.requestapi.RequestApiLogRecordAop;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
|
@ -54,96 +45,40 @@ import org.springframework.context.annotation.Configuration;
|
|||
@Configuration
|
||||
public class GunsLogAutoConfiguration {
|
||||
|
||||
/**
|
||||
* 日志配置的前缀
|
||||
*/
|
||||
public static final String SYS_LOG_PREFIX = "sys-log";
|
||||
|
||||
|
||||
/**
|
||||
* 系统日志service
|
||||
*
|
||||
* @return sysLogService
|
||||
* @author liuhanqing
|
||||
* @since 2020/12/28 22:09
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(SysLogService.class)
|
||||
@ConditionalOnProperty(prefix = SYS_LOG_PREFIX, name = "type", havingValue = "db")
|
||||
public SysLogService sysLogService() {
|
||||
return new SysLogServiceImpl();
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统日志的配置
|
||||
*
|
||||
* @author liuhanqing
|
||||
* @since 2020/12/20 14:17
|
||||
*/
|
||||
@Bean
|
||||
@ConfigurationProperties(prefix = SYS_LOG_PREFIX)
|
||||
public SysLogProperties sysLogProperties() {
|
||||
return new SysLogProperties();
|
||||
}
|
||||
|
||||
/**
|
||||
* 每个请求接口记录日志的AOP
|
||||
* 根据配置文件初始化日志记录器
|
||||
* 日志存储类型:db-数据库,file-文件,默认存储在数据库中
|
||||
* <p>
|
||||
* 根据配置文件初始化日志记录器,采用数据库存储
|
||||
*
|
||||
* @param sysLogProperties 系统日志配置文件
|
||||
* @param sysLogService 系统日志service
|
||||
* @param sysLogService 系统日志service
|
||||
* @author liuhanqing
|
||||
* @since 2020/12/20 13:02
|
||||
*/
|
||||
@Bean
|
||||
public RequestApiLogRecordAop requestApiLogRecordAop(SysLogProperties sysLogProperties, SysLogServiceImpl sysLogService) {
|
||||
|
||||
// 如果类型是文件
|
||||
if (StrUtil.isNotBlank(sysLogProperties.getType())
|
||||
&& LogSaveTypeEnum.FILE.getCode().equals(sysLogProperties.getType())) {
|
||||
|
||||
// 修改为从sys_config中获取日志存储位置
|
||||
String fileSavePath = "";
|
||||
if (SystemUtil.getOsInfo().isWindows()) {
|
||||
fileSavePath = LogConfigExpander.getLogFileSavePathWindows();
|
||||
} else {
|
||||
fileSavePath = LogConfigExpander.getLogFileSavePathLinux();
|
||||
}
|
||||
|
||||
return new RequestApiLogRecordAop(new FileLogRecordServiceImpl(fileSavePath, new LogManagerThreadPool()));
|
||||
}
|
||||
|
||||
// 其他情况用数据库存储日志
|
||||
public RequestApiLogRecordAop requestApiLogRecordAop(SysLogService sysLogService) {
|
||||
return new RequestApiLogRecordAop(new DbLogRecordServiceImpl(new LogManagerThreadPool(), sysLogService));
|
||||
}
|
||||
|
||||
/**
|
||||
* 日志管理器
|
||||
*
|
||||
* @param sysLogProperties 系统日志配置文件
|
||||
* @author liuhanqing
|
||||
* @since 2020/12/20 18:53
|
||||
*/
|
||||
@Bean
|
||||
public LogManagerApi logManagerApi(SysLogProperties sysLogProperties) {
|
||||
|
||||
// 如果类型是文件
|
||||
if (StrUtil.isNotBlank(sysLogProperties.getType())
|
||||
&& LogSaveTypeEnum.FILE.getCode().equals(sysLogProperties.getType())) {
|
||||
|
||||
// 修改为从sys_config中获取日志存储位置
|
||||
String fileSavePath = "";
|
||||
if (SystemUtil.getOsInfo().isWindows()) {
|
||||
fileSavePath = LogConfigExpander.getLogFileSavePathWindows();
|
||||
} else {
|
||||
fileSavePath = LogConfigExpander.getLogFileSavePathLinux();
|
||||
}
|
||||
|
||||
return new FileLogManagerServiceImpl(fileSavePath);
|
||||
}
|
||||
|
||||
// 其他情况用数据库存储日志
|
||||
public LogManagerApi logManagerApi() {
|
||||
return new DbLogManagerServiceImpl();
|
||||
}
|
||||
|
||||
|
@ -154,7 +89,7 @@ public class GunsLogAutoConfiguration {
|
|||
* @since 2021/3/4 22:16
|
||||
*/
|
||||
@Bean
|
||||
public LogRecordApi logRecordApi(SysLogServiceImpl sysLogService) {
|
||||
public LogRecordApi logRecordApi(SysLogService sysLogService) {
|
||||
return new DbLogRecordServiceImpl(new LogManagerThreadPool(), sysLogService);
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
<module>log-business-manage</module>
|
||||
<module>log-business-requestapi</module>
|
||||
<module>log-sdk-db</module>
|
||||
<module>log-sdk-file</module>
|
||||
<module>log-spring-boot-starter</module>
|
||||
</modules>
|
||||
|
||||
|
|
Loading…
Reference in New Issue