How to Build a Custom Java Logging Framework with Annotations and Queues
This article walks through creating a lightweight Java logging framework—from motivation and project setup to defining constants, configuration loading, utility classes, annotation processing, asynchronous message handling, and a complete test—showing how to inject values, write logs, and package the solution as a reusable JAR.
Introduction
In June 2019 the author joined a company with a three‑month probation period. To speed up a slow database‑heavy module, he created a custom logging “wheel” (framework) and was promoted after one month.
Why Build Your Own Wheel
Reasons: existing frameworks may be too heavyweight, not fit business needs, cannot be used due to security policies, or simply not available. When such problems appear, writing a small dedicated component can be the right solution.
Preparation
The tutorial assumes basic Java programming ability, especially knowledge of annotations, reflection and polymorphism.
Project Setup
Create a Maven project named LogUtil. The basic package structure is shown in the diagram.
Constants
public interface Constants {
String CONFIG_FILE_NAME = "yzlogconfig";
String CONFIG_LOG_PATH = "logpath";
String CONFIG_SACN_PATH = "scanpath";
String DEFAULT_CONTENT_PREFIX = "注入值:";
String DEFAULT_FILE_NAME = "log";
String MSG_TYPE_LOG = "log";
String LINUX_LOG_PATH = "/home/data/";
String WIN_LOG_PATH = "D:/winLog/data/";
}Configuration Loader
public class ConfigurationUtil {
private static Object lock = new Object();
private static ConfigurationUtil config = null;
private static ResourceBundle rb = null;
private ConfigurationUtil(String filename) {
rb = ResourceBundle.getBundle(filename);
}
public static ConfigurationUtil getInstance(String filename) {
synchronized (lock) {
if (config == null) {
config = new ConfigurationUtil(filename);
}
}
return config;
}
public String getValue(String key) {
return rb.containsKey(key) ? rb.getString(key) : "";
}
}Utility Classes
Three helpers are provided:
DateUtil – various date format constants and methods getDate(), getDateTime().
SystemUtil – isLinux() to detect the operating system.
FileUtil – write(String path, String str) appends text to a file, creating directories if needed.
Log Writing
public class LogUtil {
public static void write2file(String path, String fileName, String content) {
String date = DateUtil.getDate() + "/";
try {
if (path != null && !path.isEmpty()) {
FileUtil.write(path + date + fileName + ".txt",
DateUtil.getDateTime() + ":" + content + "
");
} else {
if (SystemUtil.isLinux()) {
FileUtil.write(Constants.LINUX_LOG_PATH + date + fileName + ".txt",
DateUtil.getDateTime() + ":" + content + "
");
} else {
FileUtil.write(Constants.WIN_LOG_PATH + date + fileName + ".txt",
DateUtil.getDateTime() + ":" + content + "
");
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}Message Model and Queue
public class LogMsg {
private String path;
private String content;
private String fileName;
private String msgType = "logmsg";
public LogMsg(String path, String content, String fileName) {
this.path = path;
this.content = content;
this.fileName = fileName;
MsgQueue.push(this);
}
// getters and setters omitted for brevity
@Override
public String toString() {
return "LogMsg{path='" + path + '\'' +
", content='" + content + '\'' +
", fileName='" + fileName + '\'' +
", msgType='" + msgType + '\'' + '}';
}
} public class MsgQueue {
private static Queue<LogMsg> queue = new ConcurrentLinkedDeque<>();
public static boolean push(LogMsg logMsg) { return queue.offer(logMsg); }
public static LogMsg poll() { return queue.poll(); }
public static boolean isFinash() { return !queue.isEmpty(); }
}Annotation Definition
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface YzLogWrite {
int value() default -1;
String path() default "";
String fileName() default "";
String msgPrefix() default "";
}Annotation Processing
public class DealAnnotation {
private static String LOG_PATH = ConfigurationUtil.getInstance(Constants.CONFIG_FILE_NAME)
.getValue(Constants.CONFIG_LOG_PATH);
private List<String> registyClasses = new ArrayList<>();
public void injectAndMakeMsg() {
String scanPath = ConfigurationUtil.getInstance(Constants.CONFIG_FILE_NAME)
.getValue(Constants.CONFIG_SACN_PATH);
doScanner(scanPath);
for (String className : registyClasses) {
try {
Class<?> clazz = Class.forName(className);
for (Field field : clazz.getDeclaredFields()) {
if (!field.isAnnotationPresent(YzLogWrite.class)) continue;
YzLogWrite ann = field.getAnnotation(YzLogWrite.class);
String path = ann.path().isEmpty() ? LOG_PATH : ann.path();
String content = ann.msgPrefix().isEmpty() ?
Constants.DEFAULT_CONTENT_PREFIX : ann.msgPrefix();
String fileName = ann.fileName().isEmpty() ?
Constants.DEFAULT_FILE_NAME : ann.fileName();
int value = ann.value();
new LogMsg(path, content + ":" + value, fileName);
field.setAccessible(true);
field.setInt(null, value);
}
} catch (Exception e) { e.printStackTrace(); }
}
}
private void doScanner(String scanPath) { /* classpath scanning omitted */ }
}Message Handling Thread
public class DealMsg extends Thread {
@Override
public void run() {
while (MsgQueue.isFinash()) {
LogMsg logMsg = MsgQueue.poll();
switch (logMsg.getMsgType()) {
case "logmsg":
LogUtil.write2file(logMsg.getPath(),
logMsg.getFileName(),
logMsg.getContent());
break;
default:
System.out.println("no msg");
}
}
this.interrupt();
}
@Override
public synchronized void start() { this.run(); }
}Entry Point
public class StartWork {
public static void doWork() {
new DealAnnotation().injectAndMakeMsg();
new DealMsg().start();
}
}Test Example
public class Demo {
@YzLogWrite(value = 18, msgPrefix = "记录Baldwin的年龄:")
static int age;
public static void main(String[] args) {
StartWork.doWork();
System.out.println(age);
}
}The configuration file yzlogconfig contains:
logpath=/opt/
scanpath=cn/yzstu/ttRunning the demo prints 18 on the console and creates a log file under /opt/ with a line similar to:
2020-04-06 00:17:30:记录Baldwin的年龄::18Conclusion
The article demonstrates a complete, minimal logging framework that can be packaged as a JAR and integrated into other projects. It emphasizes configurability, default values, annotation‑driven injection, and asynchronous processing via a queue.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Backend Technology
Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
