AVPKit
Logger.cpp
1 /*******************************************************************************
2  * Copyright (c) 2024, 2026, Olivier Ayache. All rights reserved.
3  *
4  * This file is part of AVPKit.
5  *
6  * AVPKit is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * AVPKit is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with AVPKit. If not, see <http://www.gnu.org/licenses/>.
18  *******************************************************************************/
19 
20 #include "Logger.h"
21 #include "JNIHelper.h"
22 #include <cstring>
23 
24 #include <iostream>
25 
26 namespace com { namespace avpkit { namespace ferry {
27  bool Logger :: mInitialized = false;
28  bool Logger :: mGlobalIsLogging[5] = { true, true, true, true, true };
29  jclass Logger :: mClass = 0;
30  jmethodID Logger :: mGetLoggerMethod = 0;
31  jmethodID Logger :: mLogMethod = 0;
32 
33  Mutex* Logger :: mClassMutex = 0;
34 
35  /*
36  * This method formats the message with
37  * file and line number if given.
38  */
39  static
40  int formatMsg(char * msgBuf, int bufLen,
41  const char *aFilename, int lineNo,
42  const char *aFormat, va_list ap)
43  {
44  int bytesWritten = 0;
45  if (msgBuf && bufLen > 0) {
46  *msgBuf = 0;
47  if (aFormat && *aFormat)
48  {
49  bytesWritten += vsnprintf(msgBuf, bufLen, aFormat, ap);
50 
51  if (aFilename && *aFilename) {
52  bufLen -= bytesWritten;
53  msgBuf += bytesWritten;
54 
55  bytesWritten += snprintf(msgBuf, bufLen, " (%s:%d)",
56  aFilename, lineNo);
57  }
58  }
59  }
60  return bytesWritten;
61  }
62  Logger :: Logger(const char* loggerName, jobject javaLogger)
63  {
64  Logger::init();
65  for(unsigned int i =0 ; i< sizeof(mIsLogging)/sizeof(bool); i++)
66  mIsLogging[i] = true;
67 
68  strncpy(mLoggerName, loggerName, sizeof(mLoggerName));
69 
70  mJavaLogger = 0;
71  JNIEnv *env=JNIHelper::sGetEnv();
72  if (env && javaLogger)
73  {
74  mJavaLogger = env->NewGlobalRef(javaLogger);
75  }
76  }
77 
78  Logger :: ~Logger()
79  {
80  JNIEnv *env=JNIHelper::sGetEnv();
81  if (env)
82  {
83  if (mJavaLogger)
84  env->DeleteGlobalRef(mJavaLogger);
85  }
86  mJavaLogger = 0;
87  }
88 
89  bool
90  Logger :: init()
91  {
92  if (!mInitialized)
93  {
94  JNIHelper::sRegisterInitializationCallback(initJavaBindings,0);
95  mInitialized=true;
96  }
97  return mInitialized;
98  }
99 
100  void
101  Logger :: initJavaBindings(JavaVM*, void*)
102  {
103  JNIEnv *env=JNIHelper::sGetEnv();
104  if (env)
105  {
106  // We're inside a JVM, let's get to work.
107  jclass cls=env->FindClass("com/avpkit/ferry/NativeLogger");
108  if (cls)
109  {
110  // and find all our methods
111 
112  mGetLoggerMethod = env->GetStaticMethodID(cls,
113  "getLogger",
114  "(Ljava/lang/String;)Lcom/avpkit/ferry/NativeLogger;"
115  );
116  mLogMethod = env->GetMethodID(cls,
117  "log",
118  "(ILjava/lang/String;)Z"
119  );
120 
121  // keep a reference around
122  mClass = (jclass)env->NewWeakGlobalRef(cls);
123 
124  if (!mClassMutex) {
125  mClassMutex = Mutex::make();
126  }
127  }
128  }
129  }
130  void
131  Logger :: shutdownJavaBindings(JavaVM* vm, void*closure)
132  {
133  Logger* pLogger = (Logger*)closure;
134  if (!vm && pLogger)
135  {
136  // we're shutting down but we were never in the vm; kill this closure.
137  delete pLogger;
138  }
139  }
140 
141 
142  Logger *
143  Logger :: getLogger(const char * loggerName)
144  {
145 
146  Logger* retval = 0;
147  if (loggerName && *loggerName)
148  {
149  if (mGetLoggerMethod)
150  {
151  JNIEnv *env=JNIHelper::sGetEnv();
152  if (env)
153  {
154  jobject javaLogger = 0;
155  jstring jLoggerName = env->NewStringUTF(loggerName);
156  // Time to get a Java Logger
157  javaLogger = env->CallStaticObjectMethod(mClass,
158  mGetLoggerMethod, jLoggerName);
159  env->DeleteLocalRef(jLoggerName);
160  jLoggerName = 0;
161 
162  retval = new Logger(loggerName, javaLogger);
163 
164  if (javaLogger)
165  env->DeleteLocalRef(javaLogger);
166  javaLogger = 0;
167  }
168  } else {
169  retval = new Logger(loggerName, 0);
170  }
171 
172  }
173  return retval;
174  }
175 
176  Logger*
177  Logger :: getStaticLogger(const char *aLoggerName)
178  {
179  Logger* logger = 0;
180 
181  logger = Logger::getLogger(aLoggerName);
182  if (logger)
183  {
184  // Register a function to kill it when the JNIHelper says we
185  // can
186  JNIHelper::sRegisterTerminationCallback(Logger::shutdownJavaBindings,
187  logger);
188  }
189  return logger;
190  }
191 
192  void
193  Logger :: setIsLogging(Level level, bool value)
194  {
195  mIsLogging[level] = value;
196  }
197 
198  void
199  Logger :: setGlobalIsLogging(Level level, bool value)
200  {
201  mGlobalIsLogging[level] = value;
202  }
203 
204  bool
205  Logger :: doLog(Level level, const char *msg)
206  {
207  bool didLog = false;
208  if (mGlobalIsLogging[level] && mIsLogging[level])
209  {
210  if (mClassMutex)
211  {
212  didLog = this->doJavaLog(level, msg);
213  } else {
214  didLog = this->doNativeLog(level, msg);
215  }
216  }
217 
218  if (!didLog)
219  // turn off logging so we don't keep trying to log
220  // here.
221  mIsLogging[level] = didLog;
222 
223  return didLog;
224  }
225 
226  bool
227  Logger :: doNativeLog(Level level, const char *msg)
228  {
229  bool didLog = false;
230  const char * levelStr = "";
231  switch(level)
232  {
233  case LEVEL_ERROR:
234  levelStr="ERROR ";
235  didLog = true;
236  break;
237  case LEVEL_WARN:
238  levelStr = "WARN ";
239  didLog = true;
240  break;
241  case LEVEL_INFO:
242  levelStr = "INFO ";
243  didLog = true;
244  break;
245  case LEVEL_DEBUG:
246  levelStr = "DEBUG ";
247  didLog = true;
248  break;
249  case LEVEL_TRACE:
250  levelStr = "TRACE ";
251  didLog = false;
252  break;
253  }
254  if (didLog && msg && *msg)
255  {
256  std::cerr << levelStr << mLoggerName
257  << " - " << msg;
258  int len = strlen(msg);
259  if (msg[len-1] != '\n')
260  std::cerr << "\n";
261  }
262  return didLog;
263  }
264 
265  bool
266  Logger :: isLogging(Level level)
267  {
268  return mGlobalIsLogging[level] && mIsLogging[level];
269  }
270 
271  bool
272  Logger :: isGlobalLogging(Level level)
273  {
274  return mGlobalIsLogging[level];
275  }
276 
277  const char*
278  Logger :: getName()
279  {
280  return mLoggerName;
281  }
282 
283  bool
284  Logger :: doJavaLog(Level level, const char* msg)
285  {
286  bool retval = false;
287  JNIEnv *env=JNIHelper::sGetEnv();
288  if (env)
289  {
290  if (!mJavaLogger && mClassMutex)
291  {
292  // acquire our mutex log
293  mClassMutex->lock();
294  // check it again in case someone made
295  // a java logger while we waited for the lock.
296  if (!mJavaLogger)
297  {
298  // this logger was initialized BEFORE
299  // the class was initialized. Let's reset it.
300  Logger *jlogger = Logger::getLogger(mLoggerName);
301  if (jlogger)
302  {
303  mJavaLogger = jlogger->mJavaLogger;
304  jlogger->mJavaLogger = 0;
305  delete jlogger;
306  }
307  }
308  // release the mutex
309  mClassMutex->unlock();
310  }
311  if (mJavaLogger)
312  {
313  jstring javaMsg = env->NewStringUTF(msg);
314  retval = env->CallBooleanMethod(mJavaLogger,
315  mLogMethod, (int)level, javaMsg);
316  env->DeleteLocalRef(javaMsg);
317  javaMsg = 0;
318  }
319  }
320  return retval;
321  }
322 
323  bool
324  Logger :: log(const char* filename, int line, Level level, const char *fmt, ...)
325  {
326  bool didLog = false;
327  va_list ap;
328 
329  va_start(ap, fmt);
330  didLog = this->logVA(filename, line, level, fmt, ap);
331  va_end(ap);
332  return didLog;
333  }
334 
335  bool
336  Logger :: logVA(const char* filename, int line, Level level, const char *fmt, va_list ap)
337  {
338  bool didLog = false;
339  if (mGlobalIsLogging[level] && mIsLogging[level])
340  {
341  char msg[cMaxLogMessageLength+1];
342  formatMsg(msg, sizeof(msg), filename, line, fmt, ap);
343  didLog = this->doLog(level, msg);
344  }
345  return didLog;
346  }
347 
348 #define VS_LOGGER_CONVENIENCE_METHOD(__FUNCNAME, __LEVEL) \
349  bool \
350  Logger :: __FUNCNAME(const char* filename, int line, const char * fmt, ...) \
351  { \
352  Level level = __LEVEL; \
353  bool didLog = false; \
354  va_list ap; \
355  va_start(ap, fmt); \
356  if (mGlobalIsLogging[level] && mIsLogging[level]) \
357  { \
358  char msg[cMaxLogMessageLength+1]; \
359  formatMsg(msg, sizeof(msg), filename, line, fmt, ap); \
360  didLog = this->doLog(level, msg); \
361  } \
362  va_end(ap); \
363  return didLog; \
364  }
365  VS_LOGGER_CONVENIENCE_METHOD(error, LEVEL_ERROR)
366  VS_LOGGER_CONVENIENCE_METHOD(warn, LEVEL_WARN)
367  VS_LOGGER_CONVENIENCE_METHOD(info, LEVEL_INFO)
368  VS_LOGGER_CONVENIENCE_METHOD(debug, LEVEL_DEBUG)
369  VS_LOGGER_CONVENIENCE_METHOD(trace, LEVEL_TRACE)
370 }}}
Internal Only.
Definition: Logger.h:48
static Logger * getStaticLogger(const char *aLoggerName)
Get a Logger object, but ask the Logger code to free it up once the JavaVM shuts down.
Definition: Logger.cpp:177
Level
Different logging levels (noiseness) supported by us.
Definition: Logger.h:54
bool log(const char *filename, int lineNo, Level level, const char *format,...)
Log the message to the logger, using sprintf() format strings.
Definition: Logger.cpp:324
static Logger * getLogger(const char *aLoggerName)
Returns a new Logger object for this loggerName.
Definition: Logger.cpp:143
WARNING: Do not use logging in this class, and do not set any static file variables to values other t...