RocksJava - JNI Logger callback

Summary:
Within this commit a new AbstractLogger was introduced
which allows to handle log messages at an application level.

Log messages are passed up to Java using a JNI callback.

This allows a Java-Developer to use common Java APIs for log
messages e.g. SLF4J, LOG4J, etc. Within this commit no new
dependencies were introduced, which keeps the RocksDB API clean
and doesn`t force a developer to use a predefined high-level Java API.

Another feature is to dynamically set a custom loggers verbosity at
runtime using its public method `setInfoLogLevel` and to retrieve
the currently active level using the `infoLogLevel` method.

Test Plan:
make clean jclean rocksdbjava jtest
mvn -f rocksjni.pom package

Reviewers: adamretter, ankgup87, yhchiang

Subscribers: dhruba

Differential Revision: https://reviews.facebook.net/D34755
main
fyrz 10 years ago
parent 814627af3d
commit 57f2a00c6f
  1. 6
      java/Makefile
  2. 78
      java/rocksjni/loggerjnicallback.cc
  3. 43
      java/rocksjni/loggerjnicallback.h
  4. 11
      java/rocksjni/portal.h
  5. 2
      java/src/main/java/org/rocksdb/DBOptions.java
  6. 4
      java/src/main/java/org/rocksdb/DBOptionsInterface.java
  7. 13
      java/src/main/java/org/rocksdb/Logger.java
  8. 2
      java/src/main/java/org/rocksdb/Options.java
  9. 40
      java/src/test/java/org/rocksdb/LoggerTest.java

@ -1,5 +1,4 @@
NATIVE_JAVA_CLASSES = org.rocksdb.AbstractComparator\
org.rocksdb.AbstractLogger\
org.rocksdb.AbstractSlice\
org.rocksdb.BackupableDB\
org.rocksdb.BackupableDBOptions\
@ -18,6 +17,7 @@ NATIVE_JAVA_CLASSES = org.rocksdb.AbstractComparator\
org.rocksdb.GenericRateLimiterConfig\
org.rocksdb.HashLinkedListMemTableConfig\
org.rocksdb.HashSkipListMemTableConfig\
org.rocksdb.Logger\
org.rocksdb.MergeOperator\
org.rocksdb.Options\
org.rocksdb.PlainTableConfig\
@ -55,8 +55,7 @@ ifeq ($(PLATFORM), OS_MACOSX)
ROCKSDB_JAR = rocksdbjni-$(ROCKSDB_MAJOR).$(ROCKSDB_MINOR).$(ROCKSDB_PATCH)-osx.jar
endif
JAVA_TESTS = org.rocksdb.AbstractLoggerTest\
org.rocksdb.BackupableDBOptionsTest\
JAVA_TESTS = org.rocksdb.BackupableDBOptionsTest\
org.rocksdb.BackupableDBTest\
org.rocksdb.BlockBasedTableConfigTest\
org.rocksdb.CheckPointTest\
@ -73,6 +72,7 @@ JAVA_TESTS = org.rocksdb.AbstractLoggerTest\
org.rocksdb.FlushTest\
org.rocksdb.InfoLogLevelTest\
org.rocksdb.KeyMayExistTest\
org.rocksdb.LoggerTest\
org.rocksdb.MemTableTest\
org.rocksdb.MergeTest\
org.rocksdb.MixedOptionsTest\

@ -1,12 +1,12 @@
// Copyright (c) 2014, Facebook, Inc. All rights reserved.
// Copyright (c) 2015, Facebook, Inc. All rights reserved.
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
//
// This file implements the callback "bridge" between Java and C++ for
// rocksdb::Comparator.
// rocksdb::Logger.
#include "include/org_rocksdb_AbstractLogger.h"
#include "include/org_rocksdb_Logger.h"
#include "rocksjni/loggerjnicallback.h"
#include "rocksjni/portal.h"
@ -14,15 +14,15 @@
namespace rocksdb {
LoggerJniCallback::LoggerJniCallback(
JNIEnv* env, jobject jAbstractLogger) {
JNIEnv* env, jobject jlogger) {
const jint rs = env->GetJavaVM(&m_jvm);
assert(rs == JNI_OK);
// Note: we want to access the Java Logger instance
// across multiple method calls, so we create a global ref
m_jAbstractLogger = env->NewGlobalRef(jAbstractLogger);
m_jLogMethodId = AbstractLoggerJni::getLogMethodId(env);
m_jLogger = env->NewGlobalRef(jlogger);
m_jLogMethodId = LoggerJni::getLogMethodId(env);
}
/**
@ -44,6 +44,27 @@ void LoggerJniCallback::Logv(const InfoLogLevel log_level,
const char* format, va_list ap) {
if (GetInfoLogLevel() <= log_level) {
JNIEnv* env = getJniEnv();
// determine InfoLogLevel java enum instance
jobject jlog_level;
switch (log_level) {
case rocksdb::InfoLogLevel::DEBUG_LEVEL:
jlog_level = InfoLogLevelJni::DEBUG_LEVEL(env);
break;
case rocksdb::InfoLogLevel::INFO_LEVEL:
jlog_level = InfoLogLevelJni::INFO_LEVEL(env);
break;
case rocksdb::InfoLogLevel::ERROR_LEVEL:
jlog_level = InfoLogLevelJni::ERROR_LEVEL(env);
break;
case rocksdb::InfoLogLevel::FATAL_LEVEL:
jlog_level = InfoLogLevelJni::FATAL_LEVEL(env);
break;
default:
jlog_level = InfoLogLevelJni::FATAL_LEVEL(env);
break;
}
// We try twice: the first time with a fixed-size stack allocated buffer,
// and the second time with a much larger dynamically allocated buffer.
char buffer[500];
@ -77,26 +98,9 @@ void LoggerJniCallback::Logv(const InfoLogLevel log_level,
assert(p < limit);
*p++ = '\0';
// determine InfoLogLevel java enum instance
jobject jlog_level;
switch(log_level) {
case rocksdb::InfoLogLevel::DEBUG_LEVEL:
jlog_level = InfoLogLevelJni::DEBUG_LEVEL(env);
break;
case rocksdb::InfoLogLevel::INFO_LEVEL:
jlog_level = InfoLogLevelJni::INFO_LEVEL(env);
break;
case rocksdb::InfoLogLevel::ERROR_LEVEL:
jlog_level = InfoLogLevelJni::ERROR_LEVEL(env);
case rocksdb::InfoLogLevel::FATAL_LEVEL:
jlog_level = InfoLogLevelJni::FATAL_LEVEL(env);
default:
jlog_level = InfoLogLevelJni::FATAL_LEVEL(env);
break;
}
// pass java string to callback handler
env->CallVoidMethod(
m_jAbstractLogger,
m_jLogger,
m_jLogMethodId,
jlog_level,
env->NewStringUTF(base));
@ -112,18 +116,18 @@ void LoggerJniCallback::Logv(const InfoLogLevel log_level,
LoggerJniCallback::~LoggerJniCallback() {
JNIEnv* env = getJniEnv();
env->DeleteGlobalRef(m_jAbstractLogger);
env->DeleteGlobalRef(m_jLogger);
m_jvm->DetachCurrentThread();
}
} // namespace rocksdb
/*
* Class: org_rocksdb_AbstractLogger
* Class: org_rocksdb_Logger
* Method: createNewLoggerOptions
* Signature: (J)V
*/
void Java_org_rocksdb_AbstractLogger_createNewLoggerOptions(
void Java_org_rocksdb_Logger_createNewLoggerOptions(
JNIEnv* env, jobject jobj, jlong joptions) {
rocksdb::LoggerJniCallback* c =
new rocksdb::LoggerJniCallback(env, jobj);
@ -133,15 +137,15 @@ void Java_org_rocksdb_AbstractLogger_createNewLoggerOptions(
std::shared_ptr<rocksdb::LoggerJniCallback> *pLoggerJniCallback =
new std::shared_ptr<rocksdb::LoggerJniCallback>;
*pLoggerJniCallback = std::shared_ptr<rocksdb::LoggerJniCallback>(c);
rocksdb::AbstractLoggerJni::setHandle(env, jobj, pLoggerJniCallback);
rocksdb::LoggerJni::setHandle(env, jobj, pLoggerJniCallback);
}
/*
* Class: org_rocksdb_AbstractLogger
* Class: org_rocksdb_Logger
* Method: createNewLoggerDbOptions
* Signature: (J)V
*/
void Java_org_rocksdb_AbstractLogger_createNewLoggerDbOptions(
void Java_org_rocksdb_Logger_createNewLoggerDbOptions(
JNIEnv* env, jobject jobj, jlong jdb_options) {
rocksdb::LoggerJniCallback* c =
new rocksdb::LoggerJniCallback(env, jobj);
@ -151,15 +155,15 @@ void Java_org_rocksdb_AbstractLogger_createNewLoggerDbOptions(
std::shared_ptr<rocksdb::LoggerJniCallback> *pLoggerJniCallback =
new std::shared_ptr<rocksdb::LoggerJniCallback>;
*pLoggerJniCallback = std::shared_ptr<rocksdb::LoggerJniCallback>(c);
rocksdb::AbstractLoggerJni::setHandle(env, jobj, pLoggerJniCallback);
rocksdb::LoggerJni::setHandle(env, jobj, pLoggerJniCallback);
}
/*
* Class: org_rocksdb_AbstractLogger
* Class: org_rocksdb_Logger
* Method: setInfoLogLevel
* Signature: (JB)V
*/
void Java_org_rocksdb_AbstractLogger_setInfoLogLevel(
void Java_org_rocksdb_Logger_setInfoLogLevel(
JNIEnv* env, jobject jobj, jlong jhandle, jbyte jlog_level) {
std::shared_ptr<rocksdb::LoggerJniCallback> *handle =
reinterpret_cast<std::shared_ptr<rocksdb::LoggerJniCallback> *>(jhandle);
@ -167,11 +171,11 @@ void Java_org_rocksdb_AbstractLogger_setInfoLogLevel(
}
/*
* Class: org_rocksdb_AbstractLogger
* Class: org_rocksdb_Logger
* Method: infoLogLevel
* Signature: (J)B
*/
jbyte Java_org_rocksdb_AbstractLogger_infoLogLevel(
jbyte Java_org_rocksdb_Logger_infoLogLevel(
JNIEnv* env, jobject jobj, jlong jhandle) {
std::shared_ptr<rocksdb::LoggerJniCallback> *handle =
reinterpret_cast<std::shared_ptr<rocksdb::LoggerJniCallback> *>(jhandle);
@ -179,11 +183,11 @@ jbyte Java_org_rocksdb_AbstractLogger_infoLogLevel(
}
/*
* Class: org_rocksdb_AbstractLogger
* Class: org_rocksdb_Logger
* Method: disposeInternal
* Signature: (J)V
*/
void Java_org_rocksdb_AbstractLogger_disposeInternal(
void Java_org_rocksdb_Logger_disposeInternal(
JNIEnv* env, jobject jobj, jlong jhandle) {
std::shared_ptr<rocksdb::LoggerJniCallback> *handle =
reinterpret_cast<std::shared_ptr<rocksdb::LoggerJniCallback> *>(jhandle);

@ -1,4 +1,4 @@
// Copyright (c) 2014, Facebook, Inc. All rights reserved.
// Copyright (c) 2015, Facebook, Inc. All rights reserved.
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
@ -17,27 +17,28 @@
namespace rocksdb {
class LoggerJniCallback : public Logger {
public:
LoggerJniCallback(JNIEnv* env, jobject jAbstractLogger);
virtual ~LoggerJniCallback();
public:
LoggerJniCallback(JNIEnv* env, jobject jLogger);
virtual ~LoggerJniCallback();
using Logger::SetInfoLogLevel;
using Logger::GetInfoLogLevel;
// Write an entry to the log file with the specified format.
virtual void Logv(const char* format, va_list ap);
// Write an entry to the log file with the specified log level
// and format. Any log with level under the internal log level
// of *this (see @SetInfoLogLevel and @GetInfoLogLevel) will not be
// printed.
virtual void Logv(const InfoLogLevel log_level, const char* format, va_list ap);
using Logger::SetInfoLogLevel;
using Logger::GetInfoLogLevel;
// Write an entry to the log file with the specified format.
virtual void Logv(const char* format, va_list ap);
// Write an entry to the log file with the specified log level
// and format. Any log with level under the internal log level
// of *this (see @SetInfoLogLevel and @GetInfoLogLevel) will not be
// printed.
virtual void Logv(const InfoLogLevel log_level,
const char* format, va_list ap);
protected:
JNIEnv* getJniEnv() const;
private:
JavaVM* m_jvm;
jobject m_jAbstractLogger;
jmethodID m_jLogMethodId;
protected:
JNIEnv* getJniEnv() const;
private:
JavaVM* m_jvm;
jobject m_jLogger;
jmethodID m_jLogMethodId;
};
}
} // namespace rocksdb
#endif
#endif // JAVA_ROCKSJNI_LOGGERJNICALLBACK_H_

@ -658,7 +658,6 @@ class WriteEntryJni {
class InfoLogLevelJni {
public:
// Get the DEBUG_LEVEL enum field of org.rocksdb.InfoLogLevel
static jobject DEBUG_LEVEL(JNIEnv* env) {
return getEnum(env, "DEBUG_LEVEL");
@ -703,16 +702,16 @@ class InfoLogLevelJni {
}
};
// The portal class for org.rocksdb.AbstractLogger
class AbstractLoggerJni : public RocksDBNativeClass<
std::shared_ptr<rocksdb::LoggerJniCallback>*, AbstractLoggerJni> {
// The portal class for org.rocksdb.Logger
class LoggerJni : public RocksDBNativeClass<
std::shared_ptr<rocksdb::LoggerJniCallback>*, LoggerJni> {
public:
static jclass getJClass(JNIEnv* env) {
return RocksDBNativeClass::getJClass(env,
"org/rocksdb/AbstractLogger");
"org/rocksdb/Logger");
}
// Get the java method `name` of org.rocksdb.AbstractLogger.
// Get the java method `name` of org.rocksdb.Logger.
static jmethodID getLogMethodId(JNIEnv* env) {
static jmethodID mid = env->GetMethodID(
getJClass(env), "log",

@ -145,7 +145,7 @@ public class DBOptions extends RocksObject implements DBOptionsInterface {
}
@Override
public DBOptions setLogger(final AbstractLogger logger) {
public DBOptions setLogger(final Logger logger) {
assert(isInitialized());
setLogger(nativeHandle_, logger.nativeHandle_);
return this;

@ -136,10 +136,10 @@ public interface DBOptionsInterface {
*
* <p>Default: nullptr</p>
*
* @param logger {@link AbstractLogger} instance.
* @param logger {@link Logger} instance.
* @return the instance of the current Object.
*/
Object setLogger(AbstractLogger logger);
Object setLogger(Logger logger);
/**
* <p>Sets the RocksDB log level. Default level is INFO</p>

@ -1,3 +1,8 @@
// Copyright (c) 2015, Facebook, Inc. All rights reserved.
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
package org.rocksdb;
/**
@ -9,7 +14,7 @@ package org.rocksdb;
* Java logging APIs like Log4j or Slf4j without keeping
* database logs in the filesystem.</p>
*/
public abstract class AbstractLogger extends RocksObject {
public abstract class Logger extends RocksObject {
/**
* <p>AbstractLogger constructor.</p>
@ -20,7 +25,7 @@ public abstract class AbstractLogger extends RocksObject {
*
* @param options {@link org.rocksdb.Options} instance.
*/
public AbstractLogger(Options options) {
public Logger(final Options options) {
createNewLoggerOptions(options.nativeHandle_);
}
@ -33,7 +38,7 @@ public abstract class AbstractLogger extends RocksObject {
*
* @param dboptions {@link org.rocksdb.DBOptions} instance.
*/
public AbstractLogger(DBOptions dboptions) {
public Logger(final DBOptions dboptions) {
createNewLoggerDbOptions(dboptions.nativeHandle_);
}
@ -42,7 +47,7 @@ public abstract class AbstractLogger extends RocksObject {
*
* @param infoLogLevel {@link org.rocksdb.InfoLogLevel} instance.
*/
public void setInfoLogLevel(InfoLogLevel infoLogLevel) {
public void setInfoLogLevel(final InfoLogLevel infoLogLevel) {
setInfoLogLevel(nativeHandle_, infoLogLevel.getValue());
}

@ -639,7 +639,7 @@ public class Options extends RocksObject
}
@Override
public Options setLogger(final AbstractLogger logger) {
public Options setLogger(final Logger logger) {
assert(isInitialized());
setLogger(nativeHandle_, logger.nativeHandle_);
return this;

@ -11,7 +11,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import static org.assertj.core.api.Assertions.assertThat;
public class AbstractLoggerTest {
public class LoggerTest {
@ClassRule
public static final RocksMemoryResource rocksMemoryResource =
new RocksMemoryResource();
@ -33,7 +33,7 @@ public class AbstractLoggerTest {
setCreateIfMissing(true);
// Create new logger with max log level passed by options
AbstractLogger abstractLogger = new AbstractLogger(options) {
Logger logger = new Logger(options) {
@Override
protected void log(InfoLogLevel infoLogLevel, String logMsg) {
assertThat(logMsg).isNotNull();
@ -43,7 +43,7 @@ public class AbstractLoggerTest {
};
// Set custom logger to options
options.setLogger(abstractLogger);
options.setLogger(logger);
db = RocksDB.open(options, dbFolder.getRoot().getAbsolutePath());
@ -71,7 +71,7 @@ public class AbstractLoggerTest {
setCreateIfMissing(true);
// Create new logger with max log level passed by options
AbstractLogger abstractLogger = new AbstractLogger(options) {
Logger logger = new Logger(options) {
@Override
protected void log(InfoLogLevel infoLogLevel, String logMsg) {
assertThat(logMsg).isNotNull();
@ -81,7 +81,7 @@ public class AbstractLoggerTest {
};
// Set custom logger to options
options.setLogger(abstractLogger);
options.setLogger(logger);
db = RocksDB.open(options, dbFolder.getRoot().getAbsolutePath());
@ -99,7 +99,7 @@ public class AbstractLoggerTest {
@Test
public void dbOptionsLogger() throws RocksDBException {
RocksDB db = null;
AbstractLogger abstractLogger = null;
Logger logger = null;
List<ColumnFamilyHandle> cfHandles = new ArrayList<>();
List<ColumnFamilyDescriptor> cfDescriptors = new ArrayList<>();
cfDescriptors.add(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY));
@ -112,7 +112,7 @@ public class AbstractLoggerTest {
setCreateIfMissing(true);
// Create new logger with max log level passed by options
abstractLogger = new AbstractLogger(options) {
logger = new Logger(options) {
@Override
protected void log(InfoLogLevel infoLogLevel, String logMsg) {
assertThat(logMsg).isNotNull();
@ -122,7 +122,7 @@ public class AbstractLoggerTest {
};
// Set custom logger to options
options.setLogger(abstractLogger);
options.setLogger(logger);
db = RocksDB.open(options, dbFolder.getRoot().getAbsolutePath(),
cfDescriptors, cfHandles);
// there should be zero messages
@ -136,15 +136,15 @@ public class AbstractLoggerTest {
if (db != null) {
db.close();
}
if (abstractLogger != null) {
abstractLogger.dispose();
if (logger != null) {
logger.dispose();
}
}
}
@Test
public void setInfoLogLevel() {
AbstractLogger abstractLogger = null;
Logger logger = null;
try {
// Setup options
final Options options = new Options().
@ -152,7 +152,7 @@ public class AbstractLoggerTest {
setCreateIfMissing(true);
// Create new logger with max log level passed by options
abstractLogger = new AbstractLogger(options) {
logger = new Logger(options) {
@Override
protected void log(InfoLogLevel infoLogLevel, String logMsg) {
assertThat(logMsg).isNotNull();
@ -160,14 +160,14 @@ public class AbstractLoggerTest {
logMessageCounter.incrementAndGet();
}
};
assertThat(abstractLogger.infoLogLevel()).
assertThat(logger.infoLogLevel()).
isEqualTo(InfoLogLevel.FATAL_LEVEL);
abstractLogger.setInfoLogLevel(InfoLogLevel.DEBUG_LEVEL);
assertThat(abstractLogger.infoLogLevel()).
logger.setInfoLogLevel(InfoLogLevel.DEBUG_LEVEL);
assertThat(logger.infoLogLevel()).
isEqualTo(InfoLogLevel.DEBUG_LEVEL);
} finally {
if (abstractLogger != null) {
abstractLogger.dispose();
if (logger != null) {
logger.dispose();
}
}
}
@ -184,7 +184,7 @@ public class AbstractLoggerTest {
setCreateIfMissing(true);
// Create new logger with max log level passed by options
AbstractLogger abstractLogger = new AbstractLogger(options) {
Logger logger = new Logger(options) {
@Override
protected void log(InfoLogLevel infoLogLevel, String logMsg) {
assertThat(logMsg).isNotNull();
@ -194,7 +194,7 @@ public class AbstractLoggerTest {
};
// Set custom logger to options
options.setLogger(abstractLogger);
options.setLogger(logger);
db = RocksDB.open(options, dbFolder.getRoot().getAbsolutePath());
// there should be zero messages
@ -202,7 +202,7 @@ public class AbstractLoggerTest {
assertThat(logMessageCounter.get()).isEqualTo(0);
// change log level to debug level
abstractLogger.setInfoLogLevel(InfoLogLevel.DEBUG_LEVEL);
logger.setInfoLogLevel(InfoLogLevel.DEBUG_LEVEL);
db.put("key".getBytes(), "value".getBytes());
db.flush(new FlushOptions().setWaitForFlush(true));
Loading…
Cancel
Save