1 // Copyright (c) 2011-present, Facebook, Inc.  All rights reserved.
2 //  This source code is licensed under both the GPLv2 (found in the
3 //  COPYING file in the root directory) and Apache 2.0 License
4 //  (found in the LICENSE.Apache file in the root directory).
5 //
6 // This file implements the callback "bridge" between Java and C++ for
7 // ROCKSDB_NAMESPACE::Logger.
8 
9 #include "include/org_rocksdb_Logger.h"
10 
11 #include <cstdarg>
12 #include <cstdio>
13 #include "rocksjni/loggerjnicallback.h"
14 #include "rocksjni/portal.h"
15 
16 namespace ROCKSDB_NAMESPACE {
17 
LoggerJniCallback(JNIEnv * env,jobject jlogger)18 LoggerJniCallback::LoggerJniCallback(JNIEnv* env, jobject jlogger)
19     : JniCallback(env, jlogger) {
20   m_jLogMethodId = LoggerJni::getLogMethodId(env);
21   if (m_jLogMethodId == nullptr) {
22     // exception thrown: NoSuchMethodException or OutOfMemoryError
23     return;
24   }
25 
26   jobject jdebug_level = InfoLogLevelJni::DEBUG_LEVEL(env);
27   if (jdebug_level == nullptr) {
28     // exception thrown: NoSuchFieldError, ExceptionInInitializerError
29     // or OutOfMemoryError
30     return;
31   }
32   m_jdebug_level = env->NewGlobalRef(jdebug_level);
33   if (m_jdebug_level == nullptr) {
34     // exception thrown: OutOfMemoryError
35     return;
36   }
37 
38   jobject jinfo_level = InfoLogLevelJni::INFO_LEVEL(env);
39   if (jinfo_level == nullptr) {
40     // exception thrown: NoSuchFieldError, ExceptionInInitializerError
41     // or OutOfMemoryError
42     return;
43   }
44   m_jinfo_level = env->NewGlobalRef(jinfo_level);
45   if (m_jinfo_level == nullptr) {
46     // exception thrown: OutOfMemoryError
47     return;
48   }
49 
50   jobject jwarn_level = InfoLogLevelJni::WARN_LEVEL(env);
51   if (jwarn_level == nullptr) {
52     // exception thrown: NoSuchFieldError, ExceptionInInitializerError
53     // or OutOfMemoryError
54     return;
55   }
56   m_jwarn_level = env->NewGlobalRef(jwarn_level);
57   if (m_jwarn_level == nullptr) {
58     // exception thrown: OutOfMemoryError
59     return;
60   }
61 
62   jobject jerror_level = InfoLogLevelJni::ERROR_LEVEL(env);
63   if (jerror_level == nullptr) {
64     // exception thrown: NoSuchFieldError, ExceptionInInitializerError
65     // or OutOfMemoryError
66     return;
67   }
68   m_jerror_level = env->NewGlobalRef(jerror_level);
69   if (m_jerror_level == nullptr) {
70     // exception thrown: OutOfMemoryError
71     return;
72   }
73 
74   jobject jfatal_level = InfoLogLevelJni::FATAL_LEVEL(env);
75   if (jfatal_level == nullptr) {
76     // exception thrown: NoSuchFieldError, ExceptionInInitializerError
77     // or OutOfMemoryError
78     return;
79   }
80   m_jfatal_level = env->NewGlobalRef(jfatal_level);
81   if (m_jfatal_level == nullptr) {
82     // exception thrown: OutOfMemoryError
83     return;
84   }
85 
86   jobject jheader_level = InfoLogLevelJni::HEADER_LEVEL(env);
87   if (jheader_level == nullptr) {
88     // exception thrown: NoSuchFieldError, ExceptionInInitializerError
89     // or OutOfMemoryError
90     return;
91   }
92   m_jheader_level = env->NewGlobalRef(jheader_level);
93   if (m_jheader_level == nullptr) {
94     // exception thrown: OutOfMemoryError
95     return;
96   }
97 }
98 
Logv(const char *,va_list)99 void LoggerJniCallback::Logv(const char* /*format*/, va_list /*ap*/) {
100   // We implement this method because it is virtual but we don't
101   // use it because we need to know about the log level.
102 }
103 
Logv(const InfoLogLevel log_level,const char * format,va_list ap)104 void LoggerJniCallback::Logv(const InfoLogLevel log_level, const char* format,
105                              va_list ap) {
106   if (GetInfoLogLevel() <= log_level) {
107     // determine InfoLogLevel java enum instance
108     jobject jlog_level;
109     switch (log_level) {
110       case ROCKSDB_NAMESPACE::InfoLogLevel::DEBUG_LEVEL:
111         jlog_level = m_jdebug_level;
112         break;
113       case ROCKSDB_NAMESPACE::InfoLogLevel::INFO_LEVEL:
114         jlog_level = m_jinfo_level;
115         break;
116       case ROCKSDB_NAMESPACE::InfoLogLevel::WARN_LEVEL:
117         jlog_level = m_jwarn_level;
118         break;
119       case ROCKSDB_NAMESPACE::InfoLogLevel::ERROR_LEVEL:
120         jlog_level = m_jerror_level;
121         break;
122       case ROCKSDB_NAMESPACE::InfoLogLevel::FATAL_LEVEL:
123         jlog_level = m_jfatal_level;
124         break;
125       case ROCKSDB_NAMESPACE::InfoLogLevel::HEADER_LEVEL:
126         jlog_level = m_jheader_level;
127         break;
128       default:
129         jlog_level = m_jfatal_level;
130         break;
131     }
132 
133     assert(format != nullptr);
134     const std::unique_ptr<char[]> msg = format_str(format, ap);
135 
136     // pass msg to java callback handler
137     jboolean attached_thread = JNI_FALSE;
138     JNIEnv* env = getJniEnv(&attached_thread);
139     assert(env != nullptr);
140 
141     jstring jmsg = env->NewStringUTF(msg.get());
142     if (jmsg == nullptr) {
143       // unable to construct string
144       if (env->ExceptionCheck()) {
145         env->ExceptionDescribe();  // print out exception to stderr
146       }
147       releaseJniEnv(attached_thread);
148       return;
149     }
150     if (env->ExceptionCheck()) {
151       // exception thrown: OutOfMemoryError
152       env->ExceptionDescribe();  // print out exception to stderr
153       env->DeleteLocalRef(jmsg);
154       releaseJniEnv(attached_thread);
155       return;
156     }
157 
158     env->CallVoidMethod(m_jcallback_obj, m_jLogMethodId, jlog_level, jmsg);
159     if (env->ExceptionCheck()) {
160       // exception thrown
161       env->ExceptionDescribe();  // print out exception to stderr
162       env->DeleteLocalRef(jmsg);
163       releaseJniEnv(attached_thread);
164       return;
165     }
166 
167     env->DeleteLocalRef(jmsg);
168     releaseJniEnv(attached_thread);
169   }
170 }
171 
format_str(const char * format,va_list ap) const172 std::unique_ptr<char[]> LoggerJniCallback::format_str(const char* format,
173                                                       va_list ap) const {
174   va_list ap_copy;
175 
176   va_copy(ap_copy, ap);
177   const size_t required =
178       vsnprintf(nullptr, 0, format, ap_copy) + 1;  // Extra space for '\0'
179   va_end(ap_copy);
180 
181   std::unique_ptr<char[]> buf(new char[required]);
182 
183   va_copy(ap_copy, ap);
184   vsnprintf(buf.get(), required, format, ap_copy);
185   va_end(ap_copy);
186 
187   return buf;
188 }
~LoggerJniCallback()189 LoggerJniCallback::~LoggerJniCallback() {
190   jboolean attached_thread = JNI_FALSE;
191   JNIEnv* env = getJniEnv(&attached_thread);
192   assert(env != nullptr);
193 
194   if (m_jdebug_level != nullptr) {
195     env->DeleteGlobalRef(m_jdebug_level);
196   }
197 
198   if (m_jinfo_level != nullptr) {
199     env->DeleteGlobalRef(m_jinfo_level);
200   }
201 
202   if (m_jwarn_level != nullptr) {
203     env->DeleteGlobalRef(m_jwarn_level);
204   }
205 
206   if (m_jerror_level != nullptr) {
207     env->DeleteGlobalRef(m_jerror_level);
208   }
209 
210   if (m_jfatal_level != nullptr) {
211     env->DeleteGlobalRef(m_jfatal_level);
212   }
213 
214   if (m_jheader_level != nullptr) {
215     env->DeleteGlobalRef(m_jheader_level);
216   }
217 
218   releaseJniEnv(attached_thread);
219 }
220 
221 }  // namespace ROCKSDB_NAMESPACE
222 
223 /*
224  * Class:     org_rocksdb_Logger
225  * Method:    createNewLoggerOptions
226  * Signature: (J)J
227  */
Java_org_rocksdb_Logger_createNewLoggerOptions(JNIEnv * env,jobject jobj,jlong joptions)228 jlong Java_org_rocksdb_Logger_createNewLoggerOptions(JNIEnv* env, jobject jobj,
229                                                      jlong joptions) {
230   auto* sptr_logger = new std::shared_ptr<ROCKSDB_NAMESPACE::LoggerJniCallback>(
231       new ROCKSDB_NAMESPACE::LoggerJniCallback(env, jobj));
232 
233   // set log level
234   auto* options = reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(joptions);
235   sptr_logger->get()->SetInfoLogLevel(options->info_log_level);
236 
237   return reinterpret_cast<jlong>(sptr_logger);
238 }
239 
240 /*
241  * Class:     org_rocksdb_Logger
242  * Method:    createNewLoggerDbOptions
243  * Signature: (J)J
244  */
Java_org_rocksdb_Logger_createNewLoggerDbOptions(JNIEnv * env,jobject jobj,jlong jdb_options)245 jlong Java_org_rocksdb_Logger_createNewLoggerDbOptions(JNIEnv* env,
246                                                        jobject jobj,
247                                                        jlong jdb_options) {
248   auto* sptr_logger = new std::shared_ptr<ROCKSDB_NAMESPACE::LoggerJniCallback>(
249       new ROCKSDB_NAMESPACE::LoggerJniCallback(env, jobj));
250 
251   // set log level
252   auto* db_options =
253       reinterpret_cast<ROCKSDB_NAMESPACE::DBOptions*>(jdb_options);
254   sptr_logger->get()->SetInfoLogLevel(db_options->info_log_level);
255 
256   return reinterpret_cast<jlong>(sptr_logger);
257 }
258 
259 /*
260  * Class:     org_rocksdb_Logger
261  * Method:    setInfoLogLevel
262  * Signature: (JB)V
263  */
Java_org_rocksdb_Logger_setInfoLogLevel(JNIEnv *,jobject,jlong jhandle,jbyte jlog_level)264 void Java_org_rocksdb_Logger_setInfoLogLevel(JNIEnv* /*env*/, jobject /*jobj*/,
265                                              jlong jhandle, jbyte jlog_level) {
266   auto* handle =
267       reinterpret_cast<std::shared_ptr<ROCKSDB_NAMESPACE::LoggerJniCallback>*>(
268           jhandle);
269   handle->get()->SetInfoLogLevel(
270       static_cast<ROCKSDB_NAMESPACE::InfoLogLevel>(jlog_level));
271 }
272 
273 /*
274  * Class:     org_rocksdb_Logger
275  * Method:    infoLogLevel
276  * Signature: (J)B
277  */
Java_org_rocksdb_Logger_infoLogLevel(JNIEnv *,jobject,jlong jhandle)278 jbyte Java_org_rocksdb_Logger_infoLogLevel(JNIEnv* /*env*/, jobject /*jobj*/,
279                                            jlong jhandle) {
280   auto* handle =
281       reinterpret_cast<std::shared_ptr<ROCKSDB_NAMESPACE::LoggerJniCallback>*>(
282           jhandle);
283   return static_cast<jbyte>(handle->get()->GetInfoLogLevel());
284 }
285 
286 /*
287  * Class:     org_rocksdb_Logger
288  * Method:    disposeInternal
289  * Signature: (J)V
290  */
Java_org_rocksdb_Logger_disposeInternal(JNIEnv *,jobject,jlong jhandle)291 void Java_org_rocksdb_Logger_disposeInternal(JNIEnv* /*env*/, jobject /*jobj*/,
292                                              jlong jhandle) {
293   auto* handle =
294       reinterpret_cast<std::shared_ptr<ROCKSDB_NAMESPACE::LoggerJniCallback>*>(
295           jhandle);
296   delete handle;  // delete std::shared_ptr
297 }
298