Logger.java
package estore.logging;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/** Writes application events to a timestamped file logger. */
public class Logger implements ILogger {
private static final DateTimeFormatter TIMESTAMP_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final DateTimeFormatter FILE_FORMATTER =
DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss");
private final Path logDirectory;
private final Path logFile;
private final BlockingQueue<String> queue;
private final Thread worker;
private final AtomicBoolean running;
public Logger() {
this(System.getProperty("user.dir"));
}
public Logger(String directory) {
this.logDirectory = Paths.get(directory);
this.logFile = resolveLogFile(this.logDirectory);
this.queue = new LinkedBlockingQueue<String>();
this.running = new AtomicBoolean(true);
this.worker = new Thread(new Runnable() {
@Override
public void run() {
while (running.get() || !queue.isEmpty()) {
try {
String entry = queue.poll(250, TimeUnit.MILLISECONDS);
if (entry == null) {
continue;
}
writeLogEntry(entry);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}, "estore-logger-worker");
this.worker.setDaemon(true);
this.worker.start();
}
@Override
public void log(String message) {
info(message);
}
@Override
public void print(String message) {
info(message);
}
@Override
public void info(String message) {
writeLog("INFO", message);
}
@Override
public void debug(String message) {
writeLog("DEBUG", message);
}
@Override
public void trace(String message) {
writeLog("TRACE", message);
}
@Override
public void error(String message) {
writeLog("ERROR", message);
}
@Override
public void close() {
if (!running.compareAndSet(true, false)) {
return;
}
drainQueue();
worker.interrupt();
try {
worker.join(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private void drainQueue() {
while (!queue.isEmpty()) {
String entry = queue.poll();
if (entry != null) {
writeLogEntry(entry);
}
}
}
private Path resolveLogFile(Path directory) {
try {
Files.createDirectories(directory);
} catch (IOException e) {
throw new IllegalStateException("Unable to create log directory", e);
}
String fileName = "log_" + LocalDateTime.now().format(FILE_FORMATTER) + ".txt";
return directory.resolve(fileName);
}
private void writeLog(String level, String message) {
if (!running.get()) {
return;
}
String timestamp = LocalDateTime.now().format(TIMESTAMP_FORMATTER);
String source = getCallerClassName();
String entry = timestamp + " [" + source + "] " + level + " - " + message;
queue.offer(entry);
}
private void writeLogEntry(String entry) {
try (BufferedWriter writer = Files.newBufferedWriter(logFile, StandardCharsets.UTF_8,
java.nio.file.StandardOpenOption.CREATE,
java.nio.file.StandardOpenOption.APPEND)) {
writer.write(entry);
writer.newLine();
} catch (IOException e) {
throw new IllegalStateException("Unable to write to log file: " + logFile, e);
}
}
private String getCallerClassName() {
StackTraceElement[] trace = Thread.currentThread().getStackTrace();
for (StackTraceElement element : trace) {
String className = element.getClassName();
if (!Objects.equals(className, Logger.class.getName())
&& !Objects.equals(className, Thread.class.getName())) {
return className;
}
}
return "unknown";
}
}