| // Copyright (c) 2011-present, Facebook, Inc. All rights reserved. |
| // This source code is licensed under both the GPLv2 (found in the |
| // COPYING file in the root directory) and Apache 2.0 License |
| // (found in the LICENSE.Apache file in the root directory). |
| // |
| // This file implements the callback "bridge" between Java and C++ for |
| // rocksdb::Logger. |
| |
| #include "include/org_rocksdb_Logger.h" |
| |
| #include "rocksjni/loggerjnicallback.h" |
| #include "rocksjni/portal.h" |
| #include <cstdarg> |
| #include <cstdio> |
| |
| namespace rocksdb { |
| |
| LoggerJniCallback::LoggerJniCallback( |
| JNIEnv* env, jobject jlogger) { |
| // Note: Logger methods may be accessed by multiple threads, |
| // so we ref the jvm not the env |
| const jint rs = env->GetJavaVM(&m_jvm); |
| if(rs != JNI_OK) { |
| // exception thrown |
| return; |
| } |
| |
| // Note: we want to access the Java Logger instance |
| // across multiple method calls, so we create a global ref |
| assert(jlogger != nullptr); |
| m_jLogger = env->NewGlobalRef(jlogger); |
| if(m_jLogger == nullptr) { |
| // exception thrown: OutOfMemoryError |
| return; |
| } |
| m_jLogMethodId = LoggerJni::getLogMethodId(env); |
| if(m_jLogMethodId == nullptr) { |
| // exception thrown: NoSuchMethodException or OutOfMemoryError |
| return; |
| } |
| |
| jobject jdebug_level = InfoLogLevelJni::DEBUG_LEVEL(env); |
| if(jdebug_level == nullptr) { |
| // exception thrown: NoSuchFieldError, ExceptionInInitializerError |
| // or OutOfMemoryError |
| return; |
| } |
| m_jdebug_level = env->NewGlobalRef(jdebug_level); |
| if(m_jdebug_level == nullptr) { |
| // exception thrown: OutOfMemoryError |
| return; |
| } |
| |
| jobject jinfo_level = InfoLogLevelJni::INFO_LEVEL(env); |
| if(jinfo_level == nullptr) { |
| // exception thrown: NoSuchFieldError, ExceptionInInitializerError |
| // or OutOfMemoryError |
| return; |
| } |
| m_jinfo_level = env->NewGlobalRef(jinfo_level); |
| if(m_jinfo_level == nullptr) { |
| // exception thrown: OutOfMemoryError |
| return; |
| } |
| |
| jobject jwarn_level = InfoLogLevelJni::WARN_LEVEL(env); |
| if(jwarn_level == nullptr) { |
| // exception thrown: NoSuchFieldError, ExceptionInInitializerError |
| // or OutOfMemoryError |
| return; |
| } |
| m_jwarn_level = env->NewGlobalRef(jwarn_level); |
| if(m_jwarn_level == nullptr) { |
| // exception thrown: OutOfMemoryError |
| return; |
| } |
| |
| jobject jerror_level = InfoLogLevelJni::ERROR_LEVEL(env); |
| if(jerror_level == nullptr) { |
| // exception thrown: NoSuchFieldError, ExceptionInInitializerError |
| // or OutOfMemoryError |
| return; |
| } |
| m_jerror_level = env->NewGlobalRef(jerror_level); |
| if(m_jerror_level == nullptr) { |
| // exception thrown: OutOfMemoryError |
| return; |
| } |
| |
| jobject jfatal_level = InfoLogLevelJni::FATAL_LEVEL(env); |
| if(jfatal_level == nullptr) { |
| // exception thrown: NoSuchFieldError, ExceptionInInitializerError |
| // or OutOfMemoryError |
| return; |
| } |
| m_jfatal_level = env->NewGlobalRef(jfatal_level); |
| if(m_jfatal_level == nullptr) { |
| // exception thrown: OutOfMemoryError |
| return; |
| } |
| |
| jobject jheader_level = InfoLogLevelJni::HEADER_LEVEL(env); |
| if(jheader_level == nullptr) { |
| // exception thrown: NoSuchFieldError, ExceptionInInitializerError |
| // or OutOfMemoryError |
| return; |
| } |
| m_jheader_level = env->NewGlobalRef(jheader_level); |
| if(m_jheader_level == nullptr) { |
| // exception thrown: OutOfMemoryError |
| return; |
| } |
| } |
| |
| void LoggerJniCallback::Logv(const char* format, va_list ap) { |
| // We implement this method because it is virtual but we don't |
| // use it because we need to know about the log level. |
| } |
| |
| void LoggerJniCallback::Logv(const InfoLogLevel log_level, |
| const char* format, va_list ap) { |
| if (GetInfoLogLevel() <= log_level) { |
| |
| // determine InfoLogLevel java enum instance |
| jobject jlog_level; |
| switch (log_level) { |
| case rocksdb::InfoLogLevel::DEBUG_LEVEL: |
| jlog_level = m_jdebug_level; |
| break; |
| case rocksdb::InfoLogLevel::INFO_LEVEL: |
| jlog_level = m_jinfo_level; |
| break; |
| case rocksdb::InfoLogLevel::WARN_LEVEL: |
| jlog_level = m_jwarn_level; |
| break; |
| case rocksdb::InfoLogLevel::ERROR_LEVEL: |
| jlog_level = m_jerror_level; |
| break; |
| case rocksdb::InfoLogLevel::FATAL_LEVEL: |
| jlog_level = m_jfatal_level; |
| break; |
| case rocksdb::InfoLogLevel::HEADER_LEVEL: |
| jlog_level = m_jheader_level; |
| break; |
| default: |
| jlog_level = m_jfatal_level; |
| break; |
| } |
| |
| assert(format != nullptr); |
| assert(ap != nullptr); |
| const std::unique_ptr<char[]> msg = format_str(format, ap); |
| |
| // pass msg to java callback handler |
| jboolean attached_thread = JNI_FALSE; |
| JNIEnv* env = JniUtil::getJniEnv(m_jvm, &attached_thread); |
| assert(env != nullptr); |
| |
| jstring jmsg = env->NewStringUTF(msg.get()); |
| if(jmsg == nullptr) { |
| // unable to construct string |
| if(env->ExceptionCheck()) { |
| env->ExceptionDescribe(); // print out exception to stderr |
| } |
| JniUtil::releaseJniEnv(m_jvm, attached_thread); |
| return; |
| } |
| if(env->ExceptionCheck()) { |
| // exception thrown: OutOfMemoryError |
| env->ExceptionDescribe(); // print out exception to stderr |
| env->DeleteLocalRef(jmsg); |
| JniUtil::releaseJniEnv(m_jvm, attached_thread); |
| return; |
| } |
| |
| env->CallVoidMethod(m_jLogger, m_jLogMethodId, jlog_level, jmsg); |
| if(env->ExceptionCheck()) { |
| // exception thrown |
| env->ExceptionDescribe(); // print out exception to stderr |
| env->DeleteLocalRef(jmsg); |
| JniUtil::releaseJniEnv(m_jvm, attached_thread); |
| return; |
| } |
| |
| env->DeleteLocalRef(jmsg); |
| JniUtil::releaseJniEnv(m_jvm, attached_thread); |
| } |
| } |
| |
| std::unique_ptr<char[]> LoggerJniCallback::format_str(const char* format, va_list ap) const { |
| va_list ap_copy; |
| |
| va_copy(ap_copy, ap); |
| const size_t required = vsnprintf(nullptr, 0, format, ap_copy) + 1; // Extra space for '\0' |
| va_end(ap_copy); |
| |
| std::unique_ptr<char[]> buf(new char[required]); |
| |
| va_copy(ap_copy, ap); |
| vsnprintf(buf.get(), required, format, ap_copy); |
| va_end(ap_copy); |
| |
| return buf; |
| } |
| |
| LoggerJniCallback::~LoggerJniCallback() { |
| jboolean attached_thread = JNI_FALSE; |
| JNIEnv* env = JniUtil::getJniEnv(m_jvm, &attached_thread); |
| assert(env != nullptr); |
| |
| if(m_jLogger != nullptr) { |
| env->DeleteGlobalRef(m_jLogger); |
| } |
| |
| if(m_jdebug_level != nullptr) { |
| env->DeleteGlobalRef(m_jdebug_level); |
| } |
| |
| if(m_jinfo_level != nullptr) { |
| env->DeleteGlobalRef(m_jinfo_level); |
| } |
| |
| if(m_jwarn_level != nullptr) { |
| env->DeleteGlobalRef(m_jwarn_level); |
| } |
| |
| if(m_jerror_level != nullptr) { |
| env->DeleteGlobalRef(m_jerror_level); |
| } |
| |
| if(m_jfatal_level != nullptr) { |
| env->DeleteGlobalRef(m_jfatal_level); |
| } |
| |
| if(m_jheader_level != nullptr) { |
| env->DeleteGlobalRef(m_jheader_level); |
| } |
| |
| JniUtil::releaseJniEnv(m_jvm, attached_thread); |
| } |
| |
| } // namespace rocksdb |
| |
| /* |
| * Class: org_rocksdb_Logger |
| * Method: createNewLoggerOptions |
| * Signature: (J)J |
| */ |
| jlong Java_org_rocksdb_Logger_createNewLoggerOptions( |
| JNIEnv* env, jobject jobj, jlong joptions) { |
| auto* sptr_logger = new std::shared_ptr<rocksdb::LoggerJniCallback>( |
| new rocksdb::LoggerJniCallback(env, jobj)); |
| |
| // set log level |
| auto* options = reinterpret_cast<rocksdb::Options*>(joptions); |
| sptr_logger->get()->SetInfoLogLevel(options->info_log_level); |
| |
| return reinterpret_cast<jlong>(sptr_logger); |
| } |
| |
| /* |
| * Class: org_rocksdb_Logger |
| * Method: createNewLoggerDbOptions |
| * Signature: (J)J |
| */ |
| jlong Java_org_rocksdb_Logger_createNewLoggerDbOptions( |
| JNIEnv* env, jobject jobj, jlong jdb_options) { |
| auto* sptr_logger = new std::shared_ptr<rocksdb::LoggerJniCallback>( |
| new rocksdb::LoggerJniCallback(env, jobj)); |
| |
| // set log level |
| auto* db_options = reinterpret_cast<rocksdb::DBOptions*>(jdb_options); |
| sptr_logger->get()->SetInfoLogLevel(db_options->info_log_level); |
| |
| return reinterpret_cast<jlong>(sptr_logger); |
| } |
| |
| /* |
| * Class: org_rocksdb_Logger |
| * Method: setInfoLogLevel |
| * Signature: (JB)V |
| */ |
| void Java_org_rocksdb_Logger_setInfoLogLevel( |
| JNIEnv* env, jobject jobj, jlong jhandle, jbyte jlog_level) { |
| auto* handle = |
| reinterpret_cast<std::shared_ptr<rocksdb::LoggerJniCallback> *>(jhandle); |
| handle->get()-> |
| SetInfoLogLevel(static_cast<rocksdb::InfoLogLevel>(jlog_level)); |
| } |
| |
| /* |
| * Class: org_rocksdb_Logger |
| * Method: infoLogLevel |
| * Signature: (J)B |
| */ |
| jbyte Java_org_rocksdb_Logger_infoLogLevel( |
| JNIEnv* env, jobject jobj, jlong jhandle) { |
| auto* handle = |
| reinterpret_cast<std::shared_ptr<rocksdb::LoggerJniCallback> *>(jhandle); |
| return static_cast<jbyte>(handle->get()->GetInfoLogLevel()); |
| } |
| |
| /* |
| * Class: org_rocksdb_Logger |
| * Method: disposeInternal |
| * Signature: (J)V |
| */ |
| void Java_org_rocksdb_Logger_disposeInternal( |
| JNIEnv* env, jobject jobj, jlong jhandle) { |
| auto* handle = |
| reinterpret_cast<std::shared_ptr<rocksdb::LoggerJniCallback> *>(jhandle); |
| delete handle; // delete std::shared_ptr |
| } |