package com.timepath.util.logging;

import java.io.IOException;
import java.io.PrintWriter;
import java.lang.management.ManagementFactory;
import java.net.Socket;
import java.text.DateFormat;
import java.text.MessageFormat;
import java.util.Date;
import java.util.Deque;
import java.util.LinkedList;
import java.util.logging.*;

public class LogIOHandler extends StreamHandler {

    private static final Logger LOG = Logger.getLogger(LogIOHandler.class.getName());
    /**
     * unique
     */
    protected final String node = ManagementFactory.getRuntimeMXBean().getName();
    @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") // pollLast() actually does get
    private final Deque<LogRecord> recordDeque = new LinkedList<>();
    private PrintWriter pw;

    public LogIOHandler() {
        setFormatter(new LogIOFormatter());
    }

    public LogIOHandler connect(final String host, final int port) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Socket sock = new Socket(host, port);
                    pw = new PrintWriter(sock.getOutputStream(), true);
                    send("+node|" + node);
                } catch (IOException ex) {
                    LOG.log(Level.SEVERE, null, ex);
                }
            }
        }).start();
        return this;
    }

    private void send(String req) {
        pw.print(req + "\r\n");
        pw.flush();
    }

    @Override
    public synchronized void publish(LogRecord record) {
        recordDeque.addLast(record);
        if (pw != null) {
            send(getFormatter().format(record));
            recordDeque.pollLast(); // remove it after sending
        }
    }

    @Override
    public synchronized void flush() {
        pw.flush();
    }

    @Override
    public synchronized void close() {
        send("-node|" + node);
        pw.close();
        pw = null;
    }

    private class LogIOFormatter extends Formatter {

        private DateFormat dateFormat;

        private LogIOFormatter() {
        }

        @Override
        public synchronized String format(LogRecord record) {
            if (dateFormat == null) {
                dateFormat = DateFormat.getDateTimeInstance();
            }
            String level = record.getLevel().getName().toLowerCase();
            String message = MessageFormat.format("{0}: <{2}::{3}> {4}: {5}",
                    dateFormat.format(new Date(record.getMillis())),
                    record.getLoggerName(),
                    record.getSourceClassName(),
                    record.getSourceMethodName(),
                    record.getLevel(),
                    formatMessage(record));
            return "+log||" + node + '|' + level + '|' + message;
        }
    }
}

@@ -29,16 +29,102 @@
 gging;%0A%0A
+import org.jetbrains.annotations.NotNull;%0Aimport org.jetbrains.annotations.Nullable;%0A%0A
 import j
@@ -350,16 +350,43 @@
 .Deque;%0A
+import java.util.Iterator;%0A
 import j
@@ -591,14 +591,19 @@
   * 
-u
+U
 nique
+ name
 %0A   
@@ -807,25 +807,22 @@
 l Deque%3C
-LogRecord
+String
 %3E record
@@ -853,22 +853,45 @@
 ();%0A    
-privat
+@Nullable%0A    private volatil
 e PrintW
@@ -976,24 +976,37 @@
 ());%0A    %7D%0A%0A
+    @NotNull%0A
     public L
@@ -1172,38 +1172,17 @@
     try 
-%7B%0A                    
+(
 Socket s
@@ -1209,17 +1209,19 @@
 t, port)
-;
+) %7B
 %0A       
@@ -1366,17 +1366,16 @@
 eption e
-x
 ) %7B%0A    
@@ -1419,17 +1419,16 @@
  null, e
-x
 );%0A     
@@ -1511,71 +1511,287 @@
    p
-rivate void send(String req) %7B%0A        pw.print(req + %22%5Cr%5Cn%22);%0A
+ublic synchronized void send(String line) %7B%0A        recordDeque.addLast(line);%0A        if (pw != null) %7B%0A            for (Iterator%3CString%3E it = recordDeque.iterator(); it.hasNext(); ) %7B%0A                pw.print(it.next() + %22%5Cr%5Cn%22);%0A                it.remove();%0A            %7D%0A    
     
@@ -1798,32 +1798,42 @@
     pw.flush();%0A
+        %7D%0A
     %7D%0A%0A    @Over
@@ -1906,184 +1906,44 @@
     
-recordDeque.addLast(record);%0A        if (pw != null) %7B%0A            send(getFormatter().format(record));%0A            recordDeque.pollLast(); // remove it after sending%0A        %7D
+send(getFormatter().format(record));
 %0A   
@@ -1995,24 +1995,54 @@
 d flush() %7B%0A
+        if (pw != null) %7B%0A    
         pw.f
@@ -2045,24 +2045,34 @@
 pw.flush();%0A
+        %7D%0A
     %7D%0A%0A    @
@@ -2142,32 +2142,62 @@
 node%7C%22 + node);%0A
+        if (pw != null) %7B%0A    
         pw.close
@@ -2196,24 +2196,34 @@
 pw.close();%0A
+        %7D%0A
         pw =
@@ -2331,45 +2331,45 @@
 rmat
-;%0A%0A        private LogIOFormatter() %7B
+ = DateFormat.getDateTimeInstance();%0A
 %0A   
@@ -2369,26 +2369,32 @@
 );%0A%0A        
-%7D%0A
+@NotNull
 %0A        @Ov
@@ -2415,29 +2415,16 @@
  public 
-synchronized 
 String f
@@ -2429,16 +2429,25 @@
  format(
+@NotNull 
 LogRecor
@@ -2462,123 +2462,8 @@
 ) %7B%0A
-            if (dateFormat == null) %7B%0A                dateFormat = DateFormat.getDateTimeInstance();%0A            %7D%0A
     

Handle disconnection