// Copyright (c) 2011-present, 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::Logger. #include "include/org_rocksdb_Logger.h" #include "rocksjni/loggerjnicallback.h" #include "rocksjni/portal.h" #include #include 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 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 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 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( new rocksdb::LoggerJniCallback(env, jobj)); // set log level auto* options = reinterpret_cast(joptions); sptr_logger->get()->SetInfoLogLevel(options->info_log_level); return reinterpret_cast(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( new rocksdb::LoggerJniCallback(env, jobj)); // set log level auto* db_options = reinterpret_cast(jdb_options); sptr_logger->get()->SetInfoLogLevel(db_options->info_log_level); return reinterpret_cast(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 *>(jhandle); handle->get()-> SetInfoLogLevel(static_cast(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 *>(jhandle); return static_cast(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 *>(jhandle); delete handle; // delete std::shared_ptr }