实现要求
- 至少支持文件输出、控制台输出二种日志输出方式,支持同时输出到文件和控制台
- 支持DEBUG/INFO/WARN/ERROR四种日志级别
实现步骤
一、定义输出级别枚举类
enum DebugLevel {
DEBUG(1, "DEBUG"), INFO(2, "INFO"),
WARN(3, "WARN"), ERROR(4, "ERROR");
int value;
String name;
DebugLevel(int value, String name) {
this.value = value;
this.name = name;
}
}
二、定义输出方式枚举类
enum PrintWay {
CONSOLE, FILE, BOTH;
}
三、编写输出策略
定义File是为了表示输出目标文件
// 输出策略抽象接口
interface Appender {
void setPrintWay(PrintWay printWay);
}
// 输出到控制台
class ConsoleAppender implements Appender {
PrintWay printWay;
public ConsoleAppender() {
setPrintWay(PrintWay.CONSOLE);
}
@Override
public void setPrintWay(PrintWay printWay) {
this.printWay = printWay;
}
}
// 输出到文件
class FileAppender implements Appender {
PrintWay printWay;
File file;
public FileAppender(String fileName) {
setPrintWay(PrintWay.FILE);
this.file = new File(fileName);
}
@Override
public void setPrintWay(PrintWay printWay) {
this.printWay = printWay;
}
}
// 同时输出到控制台和文件
class BothAppender implements Appender {
PrintWay printWay;
File file;
public BothAppender(String fileName) {
setPrintWay(PrintWay.BOTH);
this.file = new File(fileName);
}
@Override
public void setPrintWay(PrintWay printWay) {
this.printWay = printWay;
}
}
四、编写LoggerRepository
用于创建并缓存KLLogger实例
interface LoggerRepository {
KLLogger getLogger(String className);
}
final class KLLoggerRepository implements LoggerRepository {
@Override
public KLLogger getLogger(String className) {
return new KLLogger(className);
}
}
五、编写RepositorySelector对象
用于跟踪程序上下文
class RepositorySelector {
private final LoggerRepository loggerRepository;
public RepositorySelector(LoggerRepository loggerRepository) {
this.loggerRepository = loggerRepository;
}
public LoggerRepository getLoggerRepository() {
return loggerRepository;
}
}
六、编写LogManager对象
用于维护有关Loggers和日志服务的一组共享状态
class KLLoggerManger {
private static RepositorySelector repositorySelector;
public static KLLogger getLogger(final String className) {
return getLoggerRepository().getLogger(className);
}
public static LoggerRepository getLoggerRepository() {
if (Objects.isNull(repositorySelector)) {
repositorySelector = new RepositorySelector(new KLLoggerRepository());
}
return repositorySelector.getLoggerRepository();
}
}
七、编写日志类
7.1. 编写主要成员变量以及获取日志服务的静态方法
public class KLLogger {
/**
* TODO 日志级别 debug < info < warn < error
*/
protected DebugLevel level;
/**
* TODO 输出策略
*/
protected Appender appender;
/**
* TODO 类全限定名
*/
protected String className;
// 日志时间格式
protected static final SimpleDateFormat TIME_SECOND = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
// 用户标识占位符
protected static final char BEGIN_PLACEHOLDER = '{';
public KLLogger(String className) {
this.className = className;
}
public static KLLogger getLogger(Class clazz) {
return KLLoggerManger.getLogger(clazz.getName());
}
public void setAppender(Appender appender) {
this.appender = appender;
}
public void setLogLevel(DebugLevel level) {
this.level = level;
}
}
7.2. 编写日志输出方法
public void debug(String msg) {
output(new Date(), msg, DebugLevel.DEBUG);
}
public void debug(String msg, Object... var) {
output(new Date(), msg, DebugLevel.DEBUG, var);
}
public void info(String msg) {
output(new Date(), msg, DebugLevel.INFO);
}
public void info(String msg, Object... var) {
output(new Date(), msg, DebugLevel.INFO, var);
}
public void warn(String msg) {
output(new Date(), msg, DebugLevel.WARN);
}
public void warn(String msg, Object... var) {
output(new Date(), msg, DebugLevel.WARN, var);
}
public void error(String msg) {
output(new Date(), msg, DebugLevel.ERROR);
}
public void error(String msg, Object... var) {
output(new Date(), msg, DebugLevel.ERROR, var);
}
7.3. 编写日志输出选择模式方法
/**
* 选择输出模式
* @author Fang Ruichuan
* @date 2022/6/12 17:04
* @param curDate 当前时间
* @param msg 输出语句
* @param level 日志级别
* @param args 替换参数
*/
private void output(Date curDate, String msg, DebugLevel level, Object... args) {
if (Objects.nonNull(args) && args.length != 0) {
if (this.appender instanceof ConsoleAppender) {
consoleOutput(curDate, msg, level, args);
} else if (this.appender instanceof FileAppender) {
fileOutput(curDate, msg, level, args);
} else if (this.appender instanceof BothAppender) {
consoleOutput(curDate, msg, level, args);
fileOutput(curDate, msg, level, args);
}
} else {
if (this.appender instanceof ConsoleAppender) {
consoleOutput(curDate, msg, level);
} else if (this.appender instanceof FileAppender) {
fileOutput(curDate, msg, level);
} else if (this.appender instanceof BothAppender) {
consoleOutput(curDate, msg, level);
fileOutput(curDate, msg, level);
}
}
}
7.4. 编写日志输出到文件方法
/**
* 输出到文件
* @author Fang Ruichuan
* @date 2022/6/12 17:06
* @param curDate 当前时间
* @param msg 输出语句
* @param level 日志级别
* @param args 替换参数
*/
private void fileOutput(Date curDate, String msg, DebugLevel level, Object... args) {
if (Objects.nonNull(args) && args.length != 0) {
if (level.value == DebugLevel.DEBUG.value && this.level.value >= level.value) {
fileVal(curDate, msg, DebugLevel.DEBUG, args);
}
if (level.value == DebugLevel.INFO.value && this.level.value >= level.value) {
fileVal(curDate, msg, DebugLevel.INFO, args);
}
if (level.value == DebugLevel.WARN.value && this.level.value >= level.value) {
fileVal(curDate, msg, DebugLevel.WARN, args);
}
if (level.value == DebugLevel.ERROR.value && this.level.value >= level.value) {
fileVal(curDate, msg, DebugLevel.ERROR, args);
}
} else {
if (level.value == DebugLevel.DEBUG.value && this.level.value >= level.value) {
file(curDate, msg, DebugLevel.DEBUG);
}
if (level.value == DebugLevel.INFO.value && this.level.value >= level.value) {
file(curDate, msg, DebugLevel.INFO);
}
if (level.value == DebugLevel.WARN.value && this.level.value >= level.value) {
file(curDate, msg, DebugLevel.WARN);
}
if (level.value == DebugLevel.ERROR.value && this.level.value >= level.value) {
file(curDate, msg, DebugLevel.ERROR);
}
}
}
// 替换参数并输出到文件
private void fileVal(Date curDate, String msg, DebugLevel level, Object[] args) {
File file = getFileFromAppender();
String line = TIME_SECOND.format(curDate) + " [" + level.name + "] " + this.className + " - " + fillStringByArgs(msg, args);
writeWithOver(line, file);
}
// 直接输出到文件
private void file(Date curDate, String msg, DebugLevel level) {
File file = getFileFromAppender();
String line = TIME_SECOND.format(curDate) + " [" + level.name + "] " + this.className + " - " + msg;
writeWithOver(line, Objects.requireNonNull(file));
}
// 从输出策略中获取文件
private File getFileFromAppender() {
File file = null;
if (this.appender instanceof FileAppender) {
FileAppender fileAppender = (FileAppender) this.appender;
file = fileAppender.file;
} else if (this.appender instanceof BothAppender) {
BothAppender bothAppender = (BothAppender) this.appender;
file = bothAppender.file;
}
return file;
}
/**
* 将内容追加到文件中并换行
* @author Fang Ruichuan
* @date 2022/6/12 17:08
* @param line 输出内容
* @param file 目标文件地址
*/
private void writeWithOver(String line, File file) {
BufferedWriter writer = null;
try {
if (!file.exists()) {
file.createNewFile();
}
writer = new BufferedWriter(new FileWriter(file, true));
writer.write(line);
writer.newLine();
writer.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (Objects.nonNull(writer)) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
7.5. 编写日志输出到控制台方法
// 输出到控制台
private void consoleOutput(Date curDate, String msg, DebugLevel level, Object... var) {
if (Objects.nonNull(var) && var.length != 0) {
if (level.value == DebugLevel.DEBUG.value && this.level.value >= level.value) {
printVal(curDate, msg, DebugLevel.DEBUG, var);
}
if (level.value == DebugLevel.INFO.value && this.level.value >= level.value) {
printVal(curDate, msg, DebugLevel.INFO, var);
}
if (level.value == DebugLevel.WARN.value && this.level.value >= level.value) {
printVal(curDate, msg, DebugLevel.WARN, var);
}
if (level.value == DebugLevel.ERROR.value && this.level.value >= level.value) {
printVal(curDate, msg, DebugLevel.ERROR, var);
}
} else {
if (level.value == DebugLevel.DEBUG.value && this.level.value >= level.value) {
print(curDate, msg, DebugLevel.DEBUG);
}
if (level.value == DebugLevel.INFO.value && this.level.value >= level.value) {
print(curDate, msg, DebugLevel.INFO);
}
if (level.value == DebugLevel.WARN.value && this.level.value >= level.value) {
print(curDate, msg, DebugLevel.WARN);
}
if (level.value == DebugLevel.ERROR.value && this.level.value >= level.value) {
print(curDate, msg, DebugLevel.ERROR);
}
}
}
private void printVal(Date curDate, String msg, DebugLevel level, Object[] args) {
System.out.print(TIME_SECOND.format(curDate) + " [" + level.name + "] " + this.className + " - ");
System.out.println(fillStringByArgs(msg, args));
}
private void print(Date curDate, String msg, DebugLevel level) {
System.out.println(TIME_SECOND.format(curDate) + " [" + level.name + "] " + this.className + " - " + msg);
}
/**
* 将参数数组中按顺序替换到内容中的{}占位符
* 例1:hello {}, my name is {} ["frc"] => hello frc, my name is {}
* 例2:hello {}, my name is {} ["frc", "yes"] => hello frc, my name is yes
* 例3:hello {}, my name is {} ["frc", "yes", "no"] => hello frc, my name is yes
* @author Fang Ruichuan
* @date 2022/6/12 17:09
* @param msg 内容
* @param args 参数
* @return String
*/
private String fillStringByArgs(String msg, Object[] args) {
int index = 0;
for (int i = 0; i < msg.length(); i++) {
char c = msg.charAt(i);
if (c == BEGIN_PLACEHOLDER && index < args.length) {
msg = msg.replaceFirst("\\{}", args[index].toString());
index = index + 1;
}
}
return msg;
}