This is an automated email from the ASF dual-hosted git repository.
sijie pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/bookkeeper.git
The following commit(s) were added to refs/heads/master by this push:
new 301ba6f ISSUE #863: BK client error messages need to be more
descriptive
301ba6f is described below
commit 301ba6f90fafa77757990e5e0caf773530c06fe2
Author: Pasha Kuznetsov <[email protected]>
AuthorDate: Mon Dec 18 13:26:22 2017 -0800
ISSUE #863: BK client error messages need to be more descriptive
This improves logging of error codes as described in the issue #863. I only
addressed the most glaring cases where bare `rc` was logged, by introducing a
new logging utility which can be used as follows:
```
LOG.error("Closing ledger {} due to {}", ledgerId,
BKException.codeLogger(rc));
```
producing messages similar to the following:
We can also discuss if the cases similar to the following should be brought
in line with the above changes, which produces more information compared to the
new approach, including stack trace, although not the original one:
```
if (rc != BKException.Code.OK) {
LOG.error("BK error opening ledger: " + lId, BKException.create(rc));
// <------------------
finalLedgerIterCb.processResult(rc, null, null);
return;
}
```
P.S. Please also note that we (Salesforce) are starting to add commit tags
simplifying our merge process, e.g. `(bug W-3843968)` -- please let us know if
that is significant enough to be discussed separately.
Author: Pasha Kuznetsov <[email protected]>
Reviewers: Enrico Olivelli <[email protected]>, Jia Zhai <None>, Sijie
Guo <[email protected]>
This closes #868 from pasha-kuznetsov/error-code-logger, closes #863
---
.../bookkeeper/bookie/LedgerDescriptorImpl.java | 6 +-
.../apache/bookkeeper/client/BookKeeperAdmin.java | 8 +-
.../apache/bookkeeper/client/BookieInfoReader.java | 6 +-
.../client/LedgerFragmentReplicator.java | 4 +-
.../org/apache/bookkeeper/client/LedgerHandle.java | 22 ++--
.../apache/bookkeeper/client/LedgerRecoveryOp.java | 4 +-
.../apache/bookkeeper/client/UpdateLedgerOp.java | 2 +-
.../apache/bookkeeper/client/api/BKException.java | 93 +++++++++++++-
.../bookkeeper/client/api/BookKeeperApiTest.java | 43 ++++++-
.../org/apache/bookkeeper/util/LoggerOutput.java | 140 +++++++++++++++++++++
10 files changed, 300 insertions(+), 28 deletions(-)
diff --git
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/LedgerDescriptorImpl.java
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/LedgerDescriptorImpl.java
index c327afe..0649bc5 100644
---
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/LedgerDescriptorImpl.java
+++
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/LedgerDescriptorImpl.java
@@ -26,6 +26,7 @@ import io.netty.buffer.ByteBuf;
import java.io.IOException;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicBoolean;
+import org.apache.bookkeeper.client.api.BKException;
import org.apache.bookkeeper.common.util.Watcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -122,7 +123,10 @@ public class LedgerDescriptorImpl extends LedgerDescriptor
{
}
ByteBuf entry = createLedgerFenceEntry(ledgerId);
journal.logAddEntry(entry, (rc, ledgerId, entryId, addr, ctx) -> {
- LOG.debug("Record fenced state for ledger {} in journal with rc
{}", ledgerId, rc);
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Record fenced state for ledger {} in journal with
rc {}",
+ ledgerId, BKException.codeLogger(rc));
+ }
if (rc == 0) {
fenceEntryPersisted.compareAndSet(false, true);
result.set(true);
diff --git
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookKeeperAdmin.java
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookKeeperAdmin.java
index 0d6321f..85e8581 100644
---
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookKeeperAdmin.java
+++
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookKeeperAdmin.java
@@ -545,7 +545,7 @@ public class BookKeeperAdmin implements AutoCloseable {
asyncRecoverBookieData(bookiesSrc, dryrun, skipOpenLedgers, new
RecoverCallback() {
@Override
public void recoverComplete(int rc, Object ctx) {
- LOG.info("Recover bookie operation completed with rc: " + rc);
+ LOG.info("Recover bookie operation completed with rc: {}",
BKException.codeLogger(rc));
SyncObject syncObj = (SyncObject) ctx;
synchronized (syncObj) {
syncObj.rc = rc;
@@ -574,7 +574,7 @@ public class BookKeeperAdmin implements AutoCloseable {
SyncObject sync = new SyncObject();
// Call the async method to recover bookie data.
asyncRecoverBookieData(lid, bookiesSrc, dryrun, skipOpenLedgers, (rc,
ctx) -> {
- LOG.info("Recover bookie for {} completed with rc : {}", lid, rc);
+ LOG.info("Recover bookie for {} completed with rc : {}", lid,
BKException.codeLogger(rc));
SyncObject syncObject = (SyncObject) ctx;
synchronized (syncObject) {
syncObject.rc = rc;
@@ -780,7 +780,7 @@ public class BookKeeperAdmin implements AutoCloseable {
@Override
public void processResult(int rc, String path, Object ctx)
{
if (BKException.Code.OK != rc) {
- LOG.error("Failed to recover ledger {} : {}", lId,
rc);
+ LOG.error("Failed to recover ledger {} : {}", lId,
BKException.codeLogger(rc));
} else {
LOG.info("Recovered ledger {}.", lId);
}
@@ -789,7 +789,7 @@ public class BookKeeperAdmin implements AutoCloseable {
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
} catch (BKException bke) {
- LOG.warn("Error on cloing ledger handle for {}.",
lId);
+ LOG.warn("Error on closing ledger handle for {}.",
lId);
}
finalLedgerIterCb.processResult(rc, path, ctx);
}
diff --git
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookieInfoReader.java
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookieInfoReader.java
index 66926b4..7147d6c 100644
---
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookieInfoReader.java
+++
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookieInfoReader.java
@@ -339,7 +339,8 @@ public class BookieInfoReader {
BookieSocketAddress b = (BookieSocketAddress)
ctx;
if (rc != BKException.Code.OK) {
if (LOG.isErrorEnabled()) {
- LOG.error("Reading bookie info from
bookie {} failed due to error: {}.", b, rc);
+ LOG.error("Reading bookie info from
bookie {} failed due to {}",
+ b, BKException.codeLogger(rc));
}
// We reread bookies missing from the map
each time, so remove to ensure
// we get to it on the next scan
@@ -413,7 +414,8 @@ public class BookieInfoReader {
BookieSocketAddress b = (BookieSocketAddress) ctx;
if (rc != BKException.Code.OK) {
if (LOG.isErrorEnabled()) {
- LOG.error("Reading bookie info from bookie
{} failed due to error: {}.", b, rc);
+ LOG.error("Reading bookie info from bookie
{} failed due to {}",
+ b, BKException.codeLogger(rc));
}
} else {
if (LOG.isDebugEnabled()) {
diff --git
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerFragmentReplicator.java
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerFragmentReplicator.java
index b1d2b44..31d8e21 100644
---
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerFragmentReplicator.java
+++
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerFragmentReplicator.java
@@ -438,8 +438,8 @@ public class LedgerFragmentReplicator {
});
return;
} else if (rc != BKException.Code.OK) {
- LOG.error("Error updating ledger config metadata for ledgerId "
- + lh.getId() + " : " + BKException.getMessage(rc));
+ LOG.error("Error updating ledger config metadata for ledgerId
{} : {}",
+ lh.getId(), BKException.codeLogger(rc));
} else {
LOG.info("Updated ZK for ledgerId: (" + lh.getId() + " : "
+ fragmentStartId
diff --git
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerHandle.java
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerHandle.java
index 519f797..32eeab8 100644
---
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerHandle.java
+++
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerHandle.java
@@ -483,8 +483,8 @@ public class LedgerHandle implements WriteHandle {
@Override
public void safeOperationComplete(int newrc,
LedgerMetadata newMeta) {
if (newrc != BKException.Code.OK) {
- LOG.error("Error reading new metadata
from ledger {} when closing, code={}",
- ledgerId, newrc);
+ LOG.error("Error reading new metadata
from ledger {} when closing: {}",
+ ledgerId,
BKException.codeLogger(newrc));
cb.closeComplete(rc,
LedgerHandle.this, ctx);
} else {
metadata.setState(prevState);
@@ -519,7 +519,8 @@ public class LedgerHandle implements WriteHandle {
}
});
} else if (rc != BKException.Code.OK) {
- LOG.error("Error update ledger metadata for ledger
{} : {}", ledgerId, rc);
+ LOG.error("Error update ledger metadata for ledger
{} : {}",
+ ledgerId, BKException.codeLogger(rc));
cb.closeComplete(rc, LedgerHandle.this, ctx);
} else {
cb.closeComplete(BKException.Code.OK,
LedgerHandle.this, ctx);
@@ -1394,7 +1395,7 @@ public class LedgerHandle implements WriteHandle {
errorOutPendingAdds(rc);
return;
}
- LOG.error("Closing ledger {} due to error {}", ledgerId, rc);
+ LOG.error("Closing ledger {} due to {}", ledgerId,
BKException.codeLogger(rc));
asyncCloseInternal(NoopCloseCallback.instance, null, rc);
}
@@ -1657,14 +1658,15 @@ public class LedgerHandle implements WriteHandle {
@Override
public void safeOperationComplete(int newrc, LedgerMetadata newMeta) {
if (newrc != BKException.Code.OK) {
- LOG.error("[EnsembleChange-L{}-{}] : error re-reading metadata
to address ensemble change conflicts,"
- + " code=", ledgerId, ensembleChangeIdx, newrc);
+ LOG.error("[EnsembleChange-L{}-{}] : error re-reading metadata
"
+ + "to address ensemble change conflicts: {}",
+ ledgerId, ensembleChangeIdx,
BKException.codeLogger(newrc));
handleUnrecoverableErrorDuringAdd(rc);
} else {
if (!resolveConflict(newMeta)) {
LOG.error("[EnsembleChange-L{}-{}] : could not resolve
ledger metadata conflict"
- + " while changing ensemble to: {}, local meta
data is \n {} \n,"
- + " zk meta data is \n {} \n, closing ledger",
+ + " while changing ensemble to: {}, local
meta data is \n {} \n,"
+ + " zk meta data is \n {} \n, closing
ledger",
ledgerId, ensembleChangeIdx,
ensembleInfo.newEnsemble, metadata, newMeta);
handleUnrecoverableErrorDuringAdd(rc);
}
@@ -1921,7 +1923,7 @@ public class LedgerHandle implements WriteHandle {
.setEntryListener(listener)
.initiate();
} else {
- LOG.error("Error writing ledger config {} of ledger {}",
rc, ledgerId);
+ LOG.error("Error writing ledger {} config: {}", ledgerId,
BKException.codeLogger(rc));
cb.operationComplete(rc, null);
}
}
@@ -1939,7 +1941,7 @@ public class LedgerHandle implements WriteHandle {
@Override
public void closeComplete(int rc, LedgerHandle lh, Object ctx) {
if (rc != BKException.Code.OK) {
- LOG.warn("Close failed: " + BKException.getMessage(rc));
+ LOG.warn("Close failed: {}", BKException.codeLogger(rc));
}
// noop
}
diff --git
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerRecoveryOp.java
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerRecoveryOp.java
index 4be5fef..6ea21b6 100644
---
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerRecoveryOp.java
+++
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerRecoveryOp.java
@@ -240,8 +240,8 @@ class LedgerRecoveryOp implements ReadEntryListener,
AddCallback {
@Override
public void addComplete(int rc, LedgerHandle lh, long entryId, Object ctx)
{
if (rc != BKException.Code.OK) {
- LOG.error("Failure " + BKException.getMessage(rc) + " while
writing entry: " + (entryId + 1)
- + " ledger: " + lh.ledgerId + " while recovering
ledger");
+ LOG.error("Failure {} while writing entry: {} while recovering
ledger: {}",
+ BKException.codeLogger(rc), entryId + 1, lh.ledgerId);
if (callbackDone.compareAndSet(false, true)) {
// Give up, we can't recover from this error
submitCallback(rc);
diff --git
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/UpdateLedgerOp.java
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/UpdateLedgerOp.java
index 8199e67..23d6be2 100644
---
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/UpdateLedgerOp.java
+++
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/UpdateLedgerOp.java
@@ -218,7 +218,7 @@ public class UpdateLedgerOp {
return; // this is OK
} else if (BKException.Code.OK != rc) {
// open ledger failed.
- LOG.error("Get ledger metadata {} failed. Error code {}",
ledgerId, rc);
+ LOG.error("Get ledger metadata {} failed: {}", ledgerId,
BKException.codeLogger(rc));
future.setException(BKException.create(rc));
return;
}
diff --git
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/api/BKException.java
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/api/BKException.java
index 07bcf0f..825088b 100644
---
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/api/BKException.java
+++
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/api/BKException.java
@@ -15,6 +15,8 @@
*/
package org.apache.bookkeeper.client.api;
+import java.lang.reflect.Field;
+
import org.apache.bookkeeper.client.LedgerHandleAdv;
import org.apache.bookkeeper.common.annotation.InterfaceAudience.Public;
import org.apache.bookkeeper.common.annotation.InterfaceStability.Unstable;
@@ -29,6 +31,8 @@ import
org.apache.bookkeeper.common.annotation.InterfaceStability.Unstable;
public abstract class BKException extends Exception {
protected final int code;
+ private static final LogMessagePool logMessagePool = new LogMessagePool();
+
/**
* Create a new exception.
*
@@ -53,9 +57,20 @@ public abstract class BKException extends Exception {
}
/**
+ * Returns a lazy error code formatter suitable to pass to log functions.
+ *
+ * @param code the error code value
+ *
+ * @return lazy error code log formatter
+ */
+ public static Object codeLogger(int code) {
+ return logMessagePool.get(code);
+ }
+
+ /**
* Describe an error code.
*
- * @param code
+ * @param code the error code value
*
* @return the description of the error code
*/
@@ -131,7 +146,7 @@ public abstract class BKException extends Exception {
}
/**
- * Codes which represent the various exceptoin types.
+ * Codes which represent the various exception types.
*/
public interface Code {
/** A placer holder (unused). */
@@ -240,4 +255,78 @@ public abstract class BKException extends Exception {
int UnexpectedConditionException = -999;
}
+ /**
+ * Code log message pool.
+ */
+ private static class LogMessagePool {
+ private final int minCode;
+ private final String[] pool;
+
+ private LogMessagePool() {
+ Field[] fields = Code.class.getDeclaredFields();
+ this.minCode = minCode(fields);
+ this.pool = new String[-minCode + 2]; //
UnexpectedConditionException is an outlier
+ initPoolMessages(fields);
+ }
+
+ private int minCode(Field[] fields) {
+ int min = 0;
+ for (Field field : fields) {
+ int code = getFieldInt(field);
+ if (code < min && code > Code.UnexpectedConditionException) {
+ min = code;
+ }
+ }
+ return min;
+ }
+
+ private void initPoolMessages(Field[] fields) {
+ for (Field field : fields) {
+ int code = getFieldInt(field);
+ int index = poolIndex(code);
+ if (index >= 0) {
+ pool[index] = String.format("%s: %s", field.getName(),
getMessage(code));
+ }
+ }
+ }
+
+ private static int getFieldInt(Field field) {
+ try {
+ return field.getInt(null);
+ } catch (IllegalAccessException e) {
+ return -1;
+ }
+ }
+
+ private Object get(int code) {
+ int index = poolIndex(code);
+ String logMessage = index >= 0 ? pool[index] : null;
+ return logMessage != null ? logMessage : new
UnrecognizedCodeLogFormatter(code);
+ }
+
+ private int poolIndex(int code) {
+ switch (code) {
+ case Code.UnexpectedConditionException:
+ return -minCode + 1;
+ default:
+ return code <= 0 && code >= minCode ? -minCode + code : -1;
+ }
+ }
+
+ /**
+ * Unrecognized code lazy log message formatter.
+ */
+ private static class UnrecognizedCodeLogFormatter {
+ private final int code;
+
+ private UnrecognizedCodeLogFormatter(int code) {
+ this.code = code;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%d: %s", code, getMessage(code));
+ }
+ }
+ }
}
diff --git
a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/api/BookKeeperApiTest.java
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/api/BookKeeperApiTest.java
index 562ecbc..9a37122 100644
---
a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/api/BookKeeperApiTest.java
+++
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/api/BookKeeperApiTest.java
@@ -22,14 +22,19 @@ package org.apache.bookkeeper.client.api;
import static com.google.common.base.Charsets.UTF_8;
import static org.apache.bookkeeper.common.concurrent.FutureUtils.result;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.hasProperty;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import io.netty.buffer.Unpooled;
import java.nio.ByteBuffer;
import java.util.Iterator;
+import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.bookkeeper.client.BKException;
import org.apache.bookkeeper.client.BKException.BKDigestMatchException;
@@ -38,7 +43,10 @@ import
org.apache.bookkeeper.client.BKException.BKLedgerFencedException;
import org.apache.bookkeeper.client.BKException.BKNoSuchLedgerExistsException;
import org.apache.bookkeeper.client.BKException.BKUnauthorizedAccessException;
import org.apache.bookkeeper.client.MockBookKeeperTestCase;
+import org.apache.bookkeeper.util.LoggerOutput;
+import org.junit.Rule;
import org.junit.Test;
+import org.slf4j.event.LoggingEvent;
/**
* Unit tests of classes in this package.
@@ -48,6 +56,9 @@ public class BookKeeperApiTest extends MockBookKeeperTestCase
{
private static final byte[] data = "foo".getBytes(UTF_8);
private static final byte[] password = "password".getBytes(UTF_8);
+ @Rule
+ public LoggerOutput loggerOutput = new LoggerOutput();
+
@Test
public void testWriteHandle() throws Exception {
try (WriteHandle writer = result(newCreateLedgerOp()
@@ -235,6 +246,14 @@ public class BookKeeperApiTest extends
MockBookKeeperTestCase {
@Test(expected = BKLedgerFencedException.class)
public void testOpenLedgerWithRecovery() throws Exception {
+
+ loggerOutput.expect((List<LoggingEvent> logEvents) -> {
+ assertThat(logEvents, hasItem(hasProperty("message",
+ containsString("due to LedgerFencedException: "
+ + "Ledger has been fenced off. Some other client
must have opened it to read")
+ )));
+ });
+
long lId;
try (WriteHandle writer = result(newCreateLedgerOp()
.withAckQuorumSize(1)
@@ -250,10 +269,10 @@ public class BookKeeperApiTest extends
MockBookKeeperTestCase {
// open with fencing
try (ReadHandle reader = result(newOpenLedgerOp()
- .withPassword(password)
- .withRecovery(true)
- .withLedgerId(lId)
- .execute())) {
+ .withPassword(password)
+ .withRecovery(true)
+ .withLedgerId(lId)
+ .execute())) {
assertTrue(reader.isClosed());
assertEquals(1L, reader.getLastAddConfirmed());
}
@@ -335,6 +354,22 @@ public class BookKeeperApiTest extends
MockBookKeeperTestCase {
}
}
+ @Test
+ public void testBKExceptionCodeLogger() {
+ assertEquals("OK: No problem", BKException.codeLogger(0).toString());
+ assertEquals("ReadException: Error while reading ledger",
BKException.codeLogger(-1).toString());
+ assertEquals("IncorrectParameterException: Incorrect parameter input",
BKException.codeLogger(-14).toString());
+ assertEquals("LedgerFencedException: Ledger has been fenced off. Some
other client must have opened it to read",
+ BKException.codeLogger(-101).toString());
+ assertEquals("ReplicationException: Errors in replication pipeline",
BKException.codeLogger(-200).toString());
+
+ assertEquals("UnexpectedConditionException: Unexpected condition",
BKException.codeLogger(-999).toString());
+
+ assertEquals("1: Unexpected condition",
BKException.codeLogger(1).toString());
+ assertEquals("123: Unexpected condition",
BKException.codeLogger(123).toString());
+ assertEquals("-201: Unexpected condition",
BKException.codeLogger(-201).toString());
+ }
+
private static void checkEntries(LedgerEntries entries, byte[] data)
throws InterruptedException, BKException {
Iterator<LedgerEntry> iterator = entries.iterator();
diff --git
a/bookkeeper-server/src/test/java/org/apache/bookkeeper/util/LoggerOutput.java
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/util/LoggerOutput.java
new file mode 100644
index 0000000..eeb161e
--- /dev/null
+++
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/util/LoggerOutput.java
@@ -0,0 +1,140 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.bookkeeper.util;
+
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import org.apache.log4j.Appender;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.mockito.ArgumentCaptor;
+import org.slf4j.Marker;
+import org.slf4j.event.Level;
+import org.slf4j.event.LoggingEvent;
+
+/**
+ * A utility class for testing logger output.
+ */
+public class LoggerOutput implements TestRule {
+
+ private Appender logAppender;
+ private ArgumentCaptor<org.apache.log4j.spi.LoggingEvent> logEventCaptor;
+ private List<Consumer<List<LoggingEvent>>> logEventExpectations = new
ArrayList<>();
+
+ public void expect(Consumer<List<LoggingEvent>> expectation) {
+ if (logEventCaptor == null) {
+ logEventCaptor =
ArgumentCaptor.forClass(org.apache.log4j.spi.LoggingEvent.class);
+ }
+ logEventExpectations.add(expectation);
+ }
+
+ @Override
+ public Statement apply(final Statement base, Description description) {
+ return new Statement() {
+
+ @Override
+ public void evaluate() throws Throwable {
+ logAppender = mock(Appender.class);
+ Logger rootLogger = LogManager.getRootLogger();
+ rootLogger.addAppender(logAppender);
+ try {
+ base.evaluate();
+ if (!logEventExpectations.isEmpty()) {
+ verify(logAppender,
atLeastOnce()).doAppend(logEventCaptor.capture());
+ List<LoggingEvent> logEvents =
logEventCaptor.getAllValues().stream()
+ .map(LoggerOutput::toSlf4j)
+ .collect(Collectors.toList());
+ for (Consumer<List<LoggingEvent>> expectation :
logEventExpectations) {
+ expectation.accept(logEvents);
+ }
+ }
+ } finally {
+ rootLogger.removeAppender(logAppender);
+ logEventExpectations.clear();
+ logEventCaptor = null;
+ }
+ }
+ };
+ }
+
+ private static LoggingEvent toSlf4j(org.apache.log4j.spi.LoggingEvent
log4jEvent) {
+ return new LoggingEvent() {
+ @Override
+ public Level getLevel() {
+ switch (log4jEvent.getLevel().toString()) {
+ case "FATAL":
+ case "ERROR": return Level.ERROR;
+ case "WARN": return Level.WARN;
+ case "INFO": return Level.INFO;
+ case "DEBUG": return Level.DEBUG;
+ case "TRACE":
+ case "ALL":
+ case "OFF":
+ default: return Level.TRACE;
+ }
+ }
+
+ @Override
+ public Marker getMarker() {
+ return null;
+ }
+
+ @Override
+ public String getLoggerName() {
+ return log4jEvent.getLoggerName();
+ }
+
+ @Override
+ public String getMessage() {
+ return log4jEvent.getRenderedMessage();
+ }
+
+ @Override
+ public String getThreadName() {
+ return log4jEvent.getThreadName();
+ }
+
+ @Override
+ public Object[] getArgumentArray() {
+ return new Object[0];
+ }
+
+ @Override
+ public long getTimeStamp() {
+ return log4jEvent.getTimeStamp();
+ }
+
+ @Override
+ public Throwable getThrowable() {
+ return null;
+ }
+ };
+ }
+}
--
To stop receiving notification emails like this one, please contact
['"[email protected]" <[email protected]>'].