Machine Coding Problem

Logger Library

maco30maco60macoAllinfrastructurestrategy-formatter-&-asynchronous-composite-sinks
Commonly Asked By:GoogleAmazonSplunkMicrosoft

Functional Scope (In-Scope)

  • Pluggable LogLevels: Support flexible severity levels (DEBUG, INFO, WARN, ERROR, FATAL) systematically.
  • Multiple Sinks Output: Enable swappable output sinks (Console, local File log records, Remote database logs) polymorphically.
  • Non-Blocking Async Buffer Queues: Call logs in non-blocking manner using bounded consumer writing queues.
  • Extensible Formatting: Strategy formats supporting JSON as well as Text formatting configurations.
  • Graceful Shutdown Flushing: Drain log buffers completely on execution completion before shutdown.

Explicit Boundaries (Out-of-Scope)

  • No Real Network Call Sockets: Remote writing steps utilize mock endpoints rather than raw socket transports.
  • No Complex Log Rotation Scheduling: Overwrite/cleanup strategies for local logs are out-of-scope.

Thread-safe async logging setups in Java and Python:

// ─── JAVA BLUEPRINT ──────────────────────────────────────────────────────────
import java.util.*;
import java.util.concurrent.*;

enum LogLevel { DEBUG, INFO, WARN, ERROR, FATAL }

class LogRecord {
    private final long timestamp;
    private final LogLevel level;
    private final String message;
    private final String threadName;
    private final String loggerName;

    public LogRecord(String loggerName, LogLevel level, String message) {
        this.timestamp = System.currentTimeMillis();
        this.loggerName = loggerName;
        this.level = level;
        this.message = message;
        this.threadName = Thread.currentThread().getName();
    }

    public long getTimestamp() { return timestamp; }
    public LogLevel getLevel() { return level; }
    public String getMessage() { return message; }
    public String getThreadName() { return threadName; }
    public String getLoggerName() { return loggerName; }
}

interface LogFormatter {
    String format(LogRecord record);
}

class TextFormatter implements LogFormatter {
    @Override
    public String format(LogRecord record) {
        return String.format("[%d] [%s] [%s] [%s] - %s",
                record.getTimestamp(),
                record.getLevel(),
                record.getLoggerName(),
                record.getThreadName(),
                record.getMessage());
    }
}

class JsonFormatter implements LogFormatter {
    @Override
    public String format(LogRecord record) {
        return String.format("{"timestamp":%d, "level":"%s", "logger":"%s", "thread":"%s", "message":"%s"}",
                record.getTimestamp(),
                record.getLevel(),
                record.getLoggerName(),
                record.getThreadName(),
                record.getMessage().replace(""", "\""));
    }
}

interface LogSink {
    void write(LogRecord record, LogFormatter formatter);
}

class ConsoleSink implements LogSink {
    @Override
    public void write(LogRecord record, LogFormatter formatter) {
        System.out.println("[Console] " + formatter.format(record));
    }
}

class FileSink implements LogSink {
    private final List<String> lines = new CopyOnWriteArrayList<>();
    @Override
    public void write(LogRecord record, LogFormatter formatter) {
        String logLine = formatter.format(record);
        lines.add(logLine);
        // Simulate writing to physical disk
        System.out.println("[FileSink Written] " + logLine);
    }
}

class AsyncLogManager {
    private final BlockingQueue<LogRecord> queue = new LinkedBlockingQueue<>(1000);
    private final List<LogSink> sinks = new CopyOnWriteArrayList<>();
    private final ExecutorService executor = Executors.newSingleThreadExecutor();
    private final LogFormatter formatter;
    private volatile boolean running = true;

    public AsyncLogManager(LogFormatter formatter) {
        this.formatter = formatter;
        executor.submit(this::processLogs);
    }

    public void addSink(LogSink sink) { sinks.add(sink); }

    public void log(LogRecord record) {
        if (!running) return;
        if (!queue.offer(record)) {
            System.err.println("Logger buffer overflow! Log message dropped: " + record.getMessage());
        }
    }

    private void processLogs() {
        while (running || !queue.isEmpty()) {
            try {
                LogRecord record = queue.poll(100, TimeUnit.MILLISECONDS);
                if (record != null) {
                    for (LogSink sink : sinks) {
                        sink.write(record, formatter);
                    }
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
    }

    public void shutdown() {
        running = false;
        executor.shutdown();
        try {
            executor.awaitTermination(2, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

class Logger {
    private final String name;
    private final LogLevel minLevel;
    private final AsyncLogManager manager;

    public Logger(String name, LogLevel minLevel, AsyncLogManager manager) {
        this.name = name;
        this.minLevel = minLevel;
        this.manager = manager;
    }

    public void log(LogLevel level, String message) {
        if (level.ordinal() >= minLevel.ordinal()) {
            manager.log(new LogRecord(name, level, message));
        }
    }

    public void debug(String msg) { log(LogLevel.DEBUG, msg); }
    public void info(String msg) { log(LogLevel.INFO, msg); }
    public void warn(String msg) { log(LogLevel.WARN, msg); }
    public void error(String msg) { log(LogLevel.ERROR, msg); }
    public void fatal(String msg) { log(LogLevel.FATAL, msg); }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("=== RUNNING LOGGER JAVA SIMULATION ===");
        LogFormatter textFormat = new TextFormatter();
        AsyncLogManager manager = new AsyncLogManager(textFormat);
        manager.addSink(new ConsoleSink());
        manager.addSink(new FileSink());

        Logger sysLog = new Logger("SystemController", LogLevel.INFO, manager);

        // Spawn multiple concurrent logging threads
        ExecutorService concurrentPublishers = Executors.newFixedThreadPool(3);
        for (int i = 1; i <= 3; i++) {
            final int id = i;
            concurrentPublishers.submit(() -> {
                sysLog.info("Log message from thread agent #" + id);
                sysLog.debug("This is a debug log (should be ignored by INFO minimum level setup)");
                sysLog.warn("Warning trace from thread agent #" + id);
            });
        }

        concurrentPublishers.shutdown();
        concurrentPublishers.awaitTermination(1, TimeUnit.SECONDS);

        Thread.sleep(500); // Wait for background worker processing
        manager.shutdown();
    }
}