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.

Java Backend Technology
Java Backend Technology
Java Backend Technology
How to Build a Custom Java Logging Framework with Annotations and Queues

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.

Project structure
Project structure

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/tt

Running 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的年龄::18

Conclusion

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaReflectionFrameworkQueue
Java Backend Technology
Written by

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!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.