Repository: alibaba/Sentinel (java)

Issue:
Make default JUL-based logging asynchronous
Reactor blockhound detected blocking calls where RecordLog flushes logs with sync IO.

weblux（spring cloud gateway）下应该避免同步IO处理的

```java
reactor.blockhound.BlockingOperationError: Blocking call! java.io.FileOutputStream#writeBytes
	at java.io.FileOutputStream.writeBytes(FileOutputStream.java)
	Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
	|_ checkpoint ⇢ org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]
	|_ checkpoint ⇢ org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter [DefaultWebFilterChain]
	|_ checkpoint ⇢ HTTP POST "/hello/yyy?token=sadasdas" [ExceptionHandlingWebHandler]
Stack trace:
		at java.io.FileOutputStream.writeBytes(FileOutputStream.java)
		at java.io.FileOutputStream.write(FileOutputStream.java:326)
		at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
		at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140)
		at java.io.PrintStream.write(PrintStream.java:482)
		at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221)
		at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291)
		at sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:104)
		at java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:185)
		at java.io.PrintStream.write(PrintStream.java:527)
		at java.io.PrintStream.print(PrintStream.java:669)
		at java.io.PrintStream.println(PrintStream.java:806)
		at com.alibaba.csp.sentinel.log.LogBase.<clinit>(LogBase.java:72)
		at com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager$GatewayRulePropertyListener.applyToConvertedParamMap(GatewayRuleManager.java:214)
		at com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager$GatewayRulePropertyListener.applyGatewayRuleInternal(GatewayRuleManager.java:148)
		at com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager$GatewayRulePropertyListener.configLoad(GatewayRuleManager.java:124)
		at com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager$GatewayRulePropertyListener.configLoad(GatewayRuleManager.java:114)
		at com.alibaba.csp.sentinel.property.DynamicSentinelProperty.addListener(DynamicSentinelProperty.java:40)
		at com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager.<clinit>(GatewayRuleManager.java:57)
		at com.alibaba.csp.sentinel.adapter.gateway.common.param.GatewayParamParser.parseParameterFor(GatewayParamParser.java:58)
		at cn.sunline.edsp.gateway.server.filters.SentinelGatewayFilter.filter(SentinelGatewayFilter.java:71)
		at org.springframework.cloud.gateway.handler.FilteringWebHandler$GatewayFilterAdapter.filter(FilteringWebHandler.java:138)
		at org.springframework.cloud.gateway.filter.OrderedGatewayFilter.filter(OrderedGatewayFilter.java:44)
		at org.springframework.cloud.gateway.handler.FilteringWebHandler$DefaultGatewayFilterChain.lambda$filter$0(FilteringWebHandler.java:118)
		at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:44)
		at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.drain(MonoIgnoreThen.java:153)
		at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:56)
		at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)
		at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:55)
		at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)
		at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:55)
		at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)
		at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:55)
		at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)
		at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:150)
		at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:67)
		at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:121)
		at reactor.core.publisher.FluxContextStart$ContextStartSubscriber.onNext(FluxContextStart.java:103)
		at reactor.core.publisher.FluxMapFuseable$MapFuseableConditionalSubscriber.onNext(FluxMapFuseable.java:287)
		at reactor.core.publisher.FluxFilterFuseable$FilterFuseableConditionalSubscriber.onNext(FluxFilterFuseable.java:330)
		at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1712)
		at reactor.core.publisher.MonoCollect$CollectSubscriber.onComplete(MonoCollect.java:160)
		at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:136)
		at reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:252)
		at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:136)
		at reactor.netty.channel.FluxReceive.terminateReceiver(FluxReceive.java:421)
		at reactor.netty.channel.FluxReceive.drainReceiver(FluxReceive.java:211)
		at reactor.netty.channel.FluxReceive.onInboundComplete(FluxReceive.java:369)
		at reactor.netty.channel.ChannelOperations.onInboundComplete(ChannelOperations.java:367)
		at reactor.netty.http.server.HttpServerOperations.onInboundNext(HttpServerOperations.java:489)
		at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:90)
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
		at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
		at reactor.netty.http.server.HttpTrafficHandler.channelRead(HttpTrafficHandler.java:214)
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
		at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
		at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436)
		at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:321)
		at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:295)
		at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251)
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
		at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
		at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
		at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
		at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163)
		at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:714)
		at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:650)
		at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:576)
		at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
		at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
		at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
		at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
		at java.lang.Thread.run(Thread.java:748)

```

Pull request description:
fix issues/1463 : Make default JUL-based logging asynchronous
### Describe what this PR does / why we need it
Make default JUL-based logging asynchronous

### Does this pull request fix one issue?
Fixes #1463

### Describe how you did it
use java.util.concurrent.Executor, submit the log task into single thread.

### Describe how to verify it


### Special notes for reviews


Patch:
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/AsyncEntry.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/AsyncEntry.java
index 98ccb85a..b58ec7ba 100644
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/AsyncEntry.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/AsyncEntry.java
@@ -35,6 +35,10 @@ public class AsyncEntry extends CtEntry {
         super(resourceWrapper, chain, context);
     }
 
+    AsyncEntry(ResourceWrapper resourceWrapper, ProcessorSlot<Object> chain, Context context, int count, Object[] args) {
+        super(resourceWrapper, chain, context, count, args);
+    }
+
     /**
      * Remove current entry from local context, but does not exit.
      */
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/CtEntry.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/CtEntry.java
index 6c62abe8..8f4ec250 100644
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/CtEntry.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/CtEntry.java
@@ -42,7 +42,11 @@ class CtEntry extends Entry {
     protected LinkedList<BiConsumer<Context, Entry>> exitHandlers;
 
     CtEntry(ResourceWrapper resourceWrapper, ProcessorSlot<Object> chain, Context context) {
-        super(resourceWrapper);
+        this(resourceWrapper, chain, context, 1, OBJECTS0);
+    }
+
+    CtEntry(ResourceWrapper resourceWrapper, ProcessorSlot<Object> chain, Context context, int count, Object[] args) {
+        super(resourceWrapper, count, args);
         this.chain = chain;
         this.context = context;
 
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/CtSph.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/CtSph.java
index 84ebf17d..88a76f75 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/CtSph.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/CtSph.java
@@ -86,7 +86,7 @@ public class CtSph implements Sph {
             return asyncEntryWithNoChain(resourceWrapper, context);
         }
 
-        AsyncEntry asyncEntry = new AsyncEntry(resourceWrapper, chain, context);
+        AsyncEntry asyncEntry = new AsyncEntry(resourceWrapper, chain, context, count, args);
         try {
             chain.entry(context, resourceWrapper, null, count, prioritized, args);
             // Initiate the async context only when the entry successfully passed the slot chain.
@@ -143,7 +143,7 @@ public class CtSph implements Sph {
             return new CtEntry(resourceWrapper, null, context);
         }
 
-        Entry e = new CtEntry(resourceWrapper, chain, context);
+        Entry e = new CtEntry(resourceWrapper, chain, context, count, args);
         try {
             chain.entry(context, resourceWrapper, null, count, prioritized, args);
         } catch (BlockException e1) {
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/Entry.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/Entry.java
index c3114821..857e6e9b 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/Entry.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/Entry.java
@@ -53,7 +53,7 @@ import com.alibaba.csp.sentinel.context.Context;
  */
 public abstract class Entry implements AutoCloseable {
 
-    private static final Object[] OBJECTS0 = new Object[0];
+    protected static final Object[] OBJECTS0 = new Object[0];
 
     private final long createTimestamp;
     private long completeTimestamp;
@@ -69,9 +69,19 @@ public abstract class Entry implements AutoCloseable {
 
     protected final ResourceWrapper resourceWrapper;
 
+    protected final int count;
+
+    protected final Object[] args;
+
     public Entry(ResourceWrapper resourceWrapper) {
+        this(resourceWrapper, 1, OBJECTS0);
+    }
+
+    public Entry(ResourceWrapper resourceWrapper, int count, Object[] args) {
         this.resourceWrapper = resourceWrapper;
         this.createTimestamp = TimeUtil.currentTimeMillis();
+        this.count = count;
+        this.args = args;
     }
 
     public ResourceWrapper getResourceWrapper() {
@@ -80,15 +90,15 @@ public abstract class Entry implements AutoCloseable {
 
     /**
      * Complete the current resource entry and restore the entry stack in context.
-     *
+     * Do not need to carry count or args parameter, initialization does
      * @throws ErrorEntryFreeException if entry in current context does not match current entry
      */
     public void exit() throws ErrorEntryFreeException {
-        exit(1, OBJECTS0);
+        exit(count, args);
     }
 
     public void exit(int count) throws ErrorEntryFreeException {
-        exit(count, OBJECTS0);
+        exit(count, args);
     }
 
     /**
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/SphO.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/SphO.java
index 592a3a0d..77db3633 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/SphO.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/SphO.java
@@ -221,6 +221,6 @@ public class SphO {
     }
 
     public static void exit() {
-        exit(1, OBJECTS0);
+        ContextUtil.getContext().getCurEntry().exit();
     }
 }
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/ConsoleHandler.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/ConsoleHandler.java
index 589b0949..b74def65 100644
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/ConsoleHandler.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/ConsoleHandler.java
@@ -15,7 +15,16 @@
  */
 package com.alibaba.csp.sentinel.log.jul;
 
+import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory;
+
 import java.io.UnsupportedEncodingException;
+import java.util.ArrayDeque;
+import java.util.Queue;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.RejectedExecutionHandler;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
 import java.util.logging.*;
 
 /**
@@ -43,9 +52,21 @@ class ConsoleHandler extends Handler {
      */
     private StreamHandler stderrHandler;
 
+    private ExecutorService executor;
+
     public ConsoleHandler() {
         this.stdoutHandler = new StreamHandler(System.out, new CspFormatter());
         this.stderrHandler = new StreamHandler(System.err, new CspFormatter());
+
+        int corePoolSize = 1;
+        int maximumPoolSize = 1;
+        long keepAliveTime = 0;
+        /**insure the log can be recorded*/
+        int queueSize = 1024;
+        RejectedExecutionHandler handler = new LogRejectedExecutionHandler();
+        executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize,
+                keepAliveTime, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(queueSize),
+                new NamedThreadFactory("sentinel-console-log-executor", true), handler);
     }
 
     @Override
@@ -62,13 +83,7 @@ class ConsoleHandler extends Handler {
 
     @Override
     public void publish(LogRecord record) {
-        if (record.getLevel().intValue() >= Level.WARNING.intValue()) {
-            stderrHandler.publish(record);
-            stderrHandler.flush();
-        } else {
-            stdoutHandler.publish(record);
-            stdoutHandler.flush();
-        }
+        executor.execute(new LogTask(record,stdoutHandler,stderrHandler));
     }
 
     @Override
@@ -79,7 +94,67 @@ class ConsoleHandler extends Handler {
 
     @Override
     public void close() throws SecurityException {
+        /**not need to record log if process is killed.*/
+        executor.shutdown();
         stdoutHandler.close();
         stderrHandler.close();
     }
+
+    public ExecutorService getExecutor() {
+        return executor;
+    }
+
+    static class LogRejectedExecutionHandler implements RejectedExecutionHandler {
+
+        /**
+         * The period of logged rejected records.
+         */
+        private final long recordPeriod;
+
+        private Long lastRecordTime;
+
+        public LogRejectedExecutionHandler() {
+            String DEFAULT_REJECTED_RECORD_PERIOD = "60000";
+            String REJECTED_RECORD_PERIOD_KEY = "sentinel.rejected.record.period";
+            lastRecordTime = null;
+            recordPeriod = Long.parseLong(System.getProperty(REJECTED_RECORD_PERIOD_KEY, DEFAULT_REJECTED_RECORD_PERIOD));
+        }
+
+        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
+            long currentTimestamp = System.currentTimeMillis();
+            if (lastRecordTime == null || currentTimestamp - lastRecordTime > recordPeriod) {
+                System.err.println("Failed to log sentinel record with console, rejected");
+                lastRecordTime = currentTimestamp;
+            }
+        }
+
+    }
+
+    static class LogTask implements Runnable {
+        private final LogRecord record;
+        private final StreamHandler stdoutHandler;
+        private final StreamHandler stderrHandler;
+
+        public LogTask(LogRecord record,StreamHandler stdoutHandler,StreamHandler stderrHandler) {
+            this.record = record;
+            this.stdoutHandler = stdoutHandler;
+            this.stderrHandler = stderrHandler;
+        }
+
+        public void run() {
+            if (record.getLevel().intValue() >= Level.WARNING.intValue()) {
+                stderrHandler.publish(record);
+                stderrHandler.flush();
+            } else {
+                stdoutHandler.publish(record);
+                stdoutHandler.flush();
+            }
+        }
+
+        public LogRecord getRecord() {
+            return record;
+        }
+
+    }
+
 }
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/DateFileLogHandler.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/DateFileLogHandler.java
index 30a5ebee..0fb13a4f 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/DateFileLogHandler.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/DateFileLogHandler.java
@@ -15,11 +15,20 @@
  */
 package com.alibaba.csp.sentinel.log.jul;
 
+import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory;
+
 import java.io.File;
 import java.io.IOException;
 import java.text.SimpleDateFormat;
+import java.util.ArrayDeque;
 import java.util.Calendar;
 import java.util.Date;
+import java.util.Queue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.RejectedExecutionHandler;
+import java.util.concurrent.TimeUnit;
 import java.util.logging.FileHandler;
 import java.util.logging.Formatter;
 import java.util.logging.Handler;
@@ -34,6 +43,8 @@ class DateFileLogHandler extends Handler {
         }
     };
 
+    private ExecutorService executor;
+
     private volatile FileHandler handler;
 
     private final String pattern;
@@ -55,10 +66,22 @@ class DateFileLogHandler extends Handler {
         this.append = append;
         rotateDate();
         this.initialized = true;
+
+        int corePoolSize = 1;
+        int maximumPoolSize = 1;
+        long keepAliveTime = 0;
+        /**insure the log can be recorded*/
+        int queueSize = 1024;
+        RejectedExecutionHandler handler = new LogRejectedExecutionHandler();
+        executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize,
+                keepAliveTime, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(queueSize),
+                new NamedThreadFactory("sentinel-datafile-log-executor", true), handler);
     }
 
     @Override
     public void close() throws SecurityException {
+        /**not need to record log if process is killed.*/
+        executor.shutdown();
         handler.close();
     }
 
@@ -80,7 +103,8 @@ class DateFileLogHandler extends Handler {
             String msg = record.getMessage();
             record.setMessage("missed file rolling at: " + new Date(endDate) + "\n" + msg);
         }
-        handler.publish(record);
+
+        executor.execute(new LogTask(record,handler));
     }
 
     private boolean shouldRotate(LogRecord record) {
@@ -145,4 +169,47 @@ class DateFileLogHandler extends Handler {
         }
     }
 
+    static class LogRejectedExecutionHandler implements RejectedExecutionHandler {
+        /**
+         * The period of logged rejected records.
+         */
+        private final long recordPeriod;
+
+        private Long lastRecordTime;
+
+        public LogRejectedExecutionHandler() {
+            String DEFAULT_REJECTED_RECORD_PERIOD = "60000";
+            String REJECTED_RECORD_PERIOD_KEY = "sentinel.rejected.record.period";
+            lastRecordTime = null;
+            recordPeriod = Long.parseLong(System.getProperty(REJECTED_RECORD_PERIOD_KEY, DEFAULT_REJECTED_RECORD_PERIOD));
+        }
+
+        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
+            long currentTimestamp = System.currentTimeMillis();
+            if (lastRecordTime == null || currentTimestamp - lastRecordTime > recordPeriod) {
+                System.err.println("Failed to log sentinel record with datafile, rejected");
+                lastRecordTime = currentTimestamp;
+            }
+        }
+    }
+
+    static class LogTask implements Runnable {
+        private final LogRecord record;
+        private final FileHandler handler;
+
+        public LogTask(LogRecord record,FileHandler handler) {
+            this.record = record;
+            this.handler = handler;
+        }
+
+        public void run() {
+            handler.publish(record);
+        }
+
+        public LogRecord getRecord() {
+            return record;
+        }
+
+    }
+
 }


Test patch:
diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/SphUTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/SphUTest.java
index e546f87c..50291446 100755
--- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/SphUTest.java
+++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/SphUTest.java
@@ -157,4 +157,14 @@ public class SphUTest {
 
         e.exit(2, arg0, arg1);
     }
+
+    @Test
+    public void testEntryExitAutomation() throws BlockException{
+        String[] args = {"foo", "baz"};
+        int batchCount = 3;
+        Entry e = SphU.entry("testEntryExitAutomation", EntryType.IN, 3, args);
+        e.exit();
+        // The number of success is automatically updated based on batchCount when exit
+        assertEquals(batchCount, e.getCurNode().totalSuccess());
+    }
 }
diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/log/jul/ConsoleHandlerTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/log/jul/ConsoleHandlerTest.java
index b30d9b2a..4783a058 100644
--- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/log/jul/ConsoleHandlerTest.java
+++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/log/jul/ConsoleHandlerTest.java
@@ -19,6 +19,7 @@ import org.junit.Test;
 
 import java.io.ByteArrayOutputStream;
 import java.io.PrintStream;
+import java.util.concurrent.TimeUnit;
 import java.util.logging.Level;
 import java.util.logging.LogRecord;
 
@@ -35,7 +36,7 @@ import static org.junit.Assert.*;
 public class ConsoleHandlerTest {
 
     @Test
-    public void testPublish() {
+    public void testInfoPublish() {
         ByteArrayOutputStream baosOut = new ByteArrayOutputStream();
         System.setOut(new PrintStream(baosOut));
 
@@ -50,35 +51,105 @@ public class ConsoleHandlerTest {
         // Test INFO level, should log to stdout
         logRecord = new LogRecord(Level.INFO, "test info message");
         consoleHandler.publish(logRecord);
+        try {
+            consoleHandler.getExecutor().shutdown();
+            consoleHandler.getExecutor().awaitTermination(1, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+        }
+        consoleHandler.close();
         assertEquals(cspFormatter.format(logRecord), baosOut.toString());
         assertEquals("", baosErr.toString());
         baosOut.reset();
         baosErr.reset();
+    }
+
+    @Test
+    public void testWarnPublish() {
+        ByteArrayOutputStream baosOut = new ByteArrayOutputStream();
+        System.setOut(new PrintStream(baosOut));
+
+        ByteArrayOutputStream baosErr = new ByteArrayOutputStream();
+        System.setErr(new PrintStream(baosErr));
+
+        CspFormatter cspFormatter = new CspFormatter();
+        ConsoleHandler consoleHandler = new ConsoleHandler();
+
+        LogRecord logRecord;
 
         // Test INFO level, should log to stderr
         logRecord = new LogRecord(Level.WARNING, "test warning message");
         consoleHandler.publish(logRecord);
+        try {
+            consoleHandler.getExecutor().shutdown();
+            consoleHandler.getExecutor().awaitTermination(1, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+        }
+        consoleHandler.close();
         assertEquals(cspFormatter.format(logRecord), baosErr.toString());
         assertEquals("", baosOut.toString());
         baosOut.reset();
         baosErr.reset();
+    }
+
+    @Test
+    public void testFinePublish() {
+        ByteArrayOutputStream baosOut = new ByteArrayOutputStream();
+        System.setOut(new PrintStream(baosOut));
+
+        ByteArrayOutputStream baosErr = new ByteArrayOutputStream();
+        System.setErr(new PrintStream(baosErr));
+
+        CspFormatter cspFormatter = new CspFormatter();
+        ConsoleHandler consoleHandler = new ConsoleHandler();
+
+        LogRecord logRecord;
 
         // Test FINE level, no log by default
         // Default log level is INFO, to log FINE message to stdout, add following config in $JAVA_HOME/jre/lib/logging.properties
         // java.util.logging.StreamHandler.level=FINE
         logRecord = new LogRecord(Level.FINE, "test fine message");
         consoleHandler.publish(logRecord);
+        try {
+            consoleHandler.getExecutor().shutdown();
+            consoleHandler.getExecutor().awaitTermination(1, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+        }
+        consoleHandler.close();
         assertEquals("", baosOut.toString());
         assertEquals("", baosErr.toString());
         baosOut.reset();
         baosErr.reset();
+    }
 
+    @Test
+    public void testSeverePublish() {
+        ByteArrayOutputStream baosOut = new ByteArrayOutputStream();
+        System.setOut(new PrintStream(baosOut));
+
+        ByteArrayOutputStream baosErr = new ByteArrayOutputStream();
+        System.setErr(new PrintStream(baosErr));
+
+        CspFormatter cspFormatter = new CspFormatter();
+        ConsoleHandler consoleHandler = new ConsoleHandler();
+
+        LogRecord logRecord;
         // Test SEVERE level, should log to stderr
         logRecord = new LogRecord(Level.SEVERE, "test severe message");
         consoleHandler.publish(logRecord);
+        try {
+            consoleHandler.getExecutor().shutdown();
+            consoleHandler.getExecutor().awaitTermination(1, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+        }
+        consoleHandler.close();
         assertEquals(cspFormatter.format(logRecord), baosErr.toString());
         assertEquals("", baosOut.toString());
         baosOut.reset();
         baosErr.reset();
     }
+
 }