You are viewing a plain text version of this content. The canonical link for it is here.
Posted to log4j-dev@logging.apache.org by "Dion, Gerard (Space Technology)" <ge...@ngc.com> on 2007/02/06 01:39:29 UTC

Anyone Interested in a New C++ Log4J?

Log4J Users:

I exposed the Java Log4J in C++ by Wrapping it with JNI.  The interface
is C++-ish, not JNI-ish; all JNI jobjects and such returned on JNI calls
and such, like the org.apache.log4j.Logger, are wrapped and saved inside
my own C++ class instances.  So one gets instead an
org::apache::log4j::Logger instance for each new call to the Logger
interface.  Example:

static org::apache::log4j::Logger * newLogger =
org::apache::Logger::getLogger(std::string loggerName);

How do I publish this new C++ Log4J Wrapper?  It's only 250 lines of
code right now, and yet it handles most of the functionality we need.
To support all the functionality of the underlying 32,000 lines of Java
log4j code, I believe another 1000 lines of C++ might be required, at
most.  

Gerard.

Gerard Dion
Senior Project Engineer
Simulation and Modeling Group, D621
Northrop Grumman Space Technology
Mail Stop: R4/1023, One Space Park
Redondo Beach, CA, 90278
310/812-8544
gerard.dion@ngc.com

 <<Dion, Gerard (Space Technology).vcf>> 



#ifndef LOGGER_H
#define LOGGER_H

#include <cstdlib>
#include <iostream>
#include <string>
#include <jni.h>
#include <map>

#include "Utility.h"


using namespace std;

namespace org {

	namespace apache {

		namespace log4j {

			// FRIEND DATA STRUCTURE DECLARATIONS
/////////////////////////////////////////////////////////////////

			class BasicConfigurator;
			class PropertyConfigurator;

			// CLASS DECLARATION
////////////////////////////////////////////////////////////////////////
/////////

			class Logger {

			private:

			// STATIC DATA STRUCTURES
////////////////////////////////////////////////////////////////////////
////

				static	JavaVM *
jvm;
				static  JNIEnv *
env;
				static	char
PATH_SEPARATOR;
				static	string
USER_CLASSPATH;
				static  map<jobject,	Logger *>
*	rootLogger;
				static  map<string,		Logger
*>	*	loggers;   
				static  map<Logger *,	jobject>
*	loggersCrossReffed;
					
			// NON STATIC DATA STRUCTURES
////////////////////////////////////////////////////////////////////////

						jclass
jclassInstance;
						jmethodID
jmethodIDInstance;

			// FRIEND DATA STRUCTURES
////////////////////////////////////////////////////////////////////////
///

				friend
BasicConfigurator;
				friend
PropertyConfigurator;


			// PRIVATE STATIC METHODS BEGIN
////////////////////////////////////////////////////////////////////

				static  JNIEnv *
getENV() { 
					jint res = 0;		
					if (jvm == NULL) {
						res = createJVM();
					}
					return env; 
				}
				static  map<jobject,	Logger *>
*	getRootLoggerMap()			{ return rootLogger; }
				static  map<string,		Logger
*>	*	getLoggersMap()				{ return
loggers; }
				static  map<Logger *,	jobject>
*	getLoggersCrossReffedMap()	{ return loggersCrossReffed; }
			
			// PRIVATE STATIC METHODS END
//////////////////////////////////////////////////////////////////////

			public:
				
				// Creation & retrieval methods:
				static Logger * getRootLogger() 
				{
					JNIEnv * theEnv = getENV();
					if (rootLogger->begin() ==
rootLogger->end()) {

						jclass loggerClass =
theEnv->FindClass("org/apache/log4j/Logger");
						jmethodID
getRootLoggerID =
theEnv->GetStaticMethodID(loggerClass,"getRootLogger","()Lorg/apache/log
4j/Logger;");
						jobject j_rootLogger =
theEnv->CallStaticObjectMethod(loggerClass,getRootLoggerID);
	
rootLogger->insert(make_pair(j_rootLogger,new
Logger(loggerClass,getRootLoggerID)));
					
					}
					return
rootLogger->begin()->second;
				}

				static Logger * getLogger(string
sLoggerName) {

					JNIEnv * theEnv = getENV();

					map<string, Logger *>::iterator
loggersFind = loggers->find(sLoggerName);
					if (loggersFind ==
loggers->end()) {

						jstring j_loggerName =
theEnv->NewStringUTF(sLoggerName.c_str());
						jclass loggerClass =
theEnv->FindClass("org/apache/log4j/Logger");
						jmethodID getLoggerID =
theEnv->GetStaticMethodID(loggerClass,"getLogger","(Ljava/lang/String;)L
org/apache/log4j/Logger;");
						jobject j_rootLogger =
theEnv->CallStaticObjectMethod(loggerClass,getLoggerID,j_loggerName);
						Logger * newLogger = new
Logger(loggerClass,getLoggerID);
	
loggers->insert(make_pair(sLoggerName, newLogger));
	
loggersCrossReffed->insert(make_pair(newLogger,j_rootLogger));
						return newLogger;

					}
					else {
					
						return
loggersFind->second;
					}	
				}


				void trace(string message) {
					createMessage(message,"trace");
				}

				void debug(string message) {
					createMessage(message,"debug");
				}

				void info(string message) {
					createMessage(message,"info");
				}

				void warn(string message) {
					createMessage(message,"warn");
				}

				void error(string message) {
					createMessage(message,"error");
				}

				void fatal(string message) {
					createMessage(message,"fatal");
				}


				void trace(string message, long lineNum)
{
					createMessage(message + " , Line
Number : " + Utility::u_ltoa(lineNum) ,"trace");
				}

				void debug(string message, long lineNum)
{
					createMessage(message + " , Line
Number : " + Utility::u_ltoa(lineNum) ,"debug");
				}

				void info(string message, long lineNum)
{
					createMessage(message + " , Line
Number : " + Utility::u_ltoa(lineNum) ,"info");
				}

				void warn(string message, long lineNum)
{
					createMessage(message + " , Line
Number : " + Utility::u_ltoa(lineNum) ,"warn");
				}

				void error(string message, long lineNum)
{
					createMessage(message + " , Line
Number : " + Utility::u_ltoa(lineNum) ,"error");
				}

				void fatal(string message, long lineNum)
{
					createMessage(message + " , Line
Number : " + Utility::u_ltoa(lineNum) ,"fatal");
				}



				void trace(string message, long lineNum,
const char * file) {
					createMessage(message + " , Line
Number : " + Utility::u_ltoa(lineNum) + " , File : " + file ,"trace");
				}

				void debug(string message, long lineNum,
const char * file) {
					createMessage(message + " , Line
Number : " + Utility::u_ltoa(lineNum) + " , File : " + file  ,"debug");
				}

				void info(string message, long lineNum,
const char * file) {
					createMessage(message + " , Line
Number : " + Utility::u_ltoa(lineNum) + " , File : " + file  ,"info");
				}

				void warn(string message, long lineNum,
const char * file) {
					createMessage(message + " , Line
Number : " + Utility::u_ltoa(lineNum) + " , File : " + file  ,"warn");
				}

				void error(string message, long lineNum,
const char * file) {
					createMessage(message + " , Line
Number : " + Utility::u_ltoa(lineNum) + " , File : " + file  ,"error");
				}

				void fatal(string message, long lineNum,
const char * file) {
					createMessage(message + " , Line
Number : " + Utility::u_ltoa(lineNum) + " , File : " + file  ,"fatal");
				}




				void createMessage(string message,
string type) {

					JNIEnv * theEnv = getENV();
					map<Logger *, jobject>	*
crossReffedMap = getLoggersCrossReffedMap();
					map<Logger *, jobject>::iterator
get_jobject = crossReffedMap->find(this);
					jobject		jobject2call
= get_jobject->second;
					jmethodID	jobjectMethodID
= theEnv->GetMethodID(this->jclassInstance, type.c_str() ,
"(Ljava/lang/Object;)V");
					theEnv->CallVoidMethod(
jobject2call, jobjectMethodID, theEnv->NewStringUTF(message.c_str()) );

				}

			private:

				Logger(jclass classInstance, jmethodID
methodIDInstance) {
					this->jclassInstance =
classInstance;
					this->jmethodIDInstance =
methodIDInstance;
				}


				static	jint createJVM() {

					if (jvm == NULL) {
						#ifdef JNI_VERSION_1_2
						
							JavaVMInitArgs
vm_args;
							JavaVMOption
options[1];
							string option0 =
"-Djava.class.path=" + USER_CLASSPATH;
	
options[0].optionString = (char *) option0.c_str();
							vm_args.version
= 0x00010002;
							vm_args.options
= options;
							vm_args.nOptions
= 1;
	
vm_args.ignoreUnrecognized = JNI_TRUE;
							/* Create the
Java VM */
							return
JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);

						#else
						
							JDK1_1InitArgs
vm_args;
							char
classpath[1024];
							vm_args.version
= 0x00010001;
	
JNI_GetDefaultJavaVMInitArgs(&vm_args);
							/* Append
USER_CLASSPATH to the default system class path */
	
sprintf(classpath, "%s%c%s", vm_args.classpath, PATH_SEPARATOR,
USER_CLASSPATH);
	
vm_args.classpath = classpath;
							/* Create the
Java VM */
							return
JNI_CreateJavaVM(&jvm, &env, &vm_args);
						
						#endif /*
JNI_VERSION_1_2 */
					}
				}
			};

			class BasicConfigurator {

			public:
				static void configure() {
					JNIEnv *	theEnv	=
Logger::getENV();
					jclass basicConfiguratorClass =
theEnv->FindClass("org/apache/log4j/BasicConfigurator");
					jmethodID
getBasicConfiguratorMethodID = 
	
theEnv->GetStaticMethodID(basicConfiguratorClass,"configure","()V");
	
theEnv->CallStaticVoidMethod(basicConfiguratorClass,getBasicConfigurator
MethodID);
				}

			};


			class PropertyConfigurator {

			public:
				static void configure(string file) {
					JNIEnv *	theEnv	=
Logger::getENV();
					jclass propertyConfiguratorClass
= theEnv->FindClass("org/apache/log4j/PropertyConfigurator");
					jmethodID
getPropertyConfiguratorMethodID = 
	
theEnv->GetStaticMethodID(propertyConfiguratorClass,"configure","(Ljava/
lang/String;)V");
					theEnv->CallStaticVoidMethod(
	
propertyConfiguratorClass,getPropertyConfiguratorMethodID,theEnv->NewStr
ingUTF(file.c_str()));
				}

			};

		}

	}

}


#endif//LOGGER_H



#include "Logger.h"

using namespace org::apache::log4j;

JavaVM *
Logger::jvm					= NULL;
JNIEnv *
Logger::env					= NULL;
char
Logger::PATH_SEPARATOR		= ';';
string
Logger::USER_CLASSPATH		= getenv("CLASSPATH");
map<jobject,		Logger *>	*	Logger::rootLogger
= new map<jobject		,	Logger *>;
map<string,			Logger *>	*	Logger::loggers
= new map<string		,	Logger *>;    
map<Logger *,		jobject>	*
Logger::loggersCrossReffed	= new map<Logger *		,
jobject>;


RE: Anyone Interested in a New C++ Log4J?

Posted by "Dion, Gerard (Space Technology)" <ge...@ngc.com>.
Curt:

My C++ log4j JNI project is 250 file lines and 1.5 days old, and it does
90% of what I need it to do right now.  That doesn't mean it's ready for
prime time, but I'm surprised that I don't need to do anything more
right now for myself at least. I think it's a statement about Log4J
actually.  Log4j has hundreds of public methods (mostly Appender and
Appender related classes that you don't need use to work with log4j if
you don't want to). But really, you only need 10 methods or so to use
Log4J, as one will find perusing Ceki Gulcu's book.  I don't know if he
ever shows you these other methods.  A few Log4J methods and the
considerable rules one can put in a configuration file allow you to
exercise the vast log4j's capabilities.  And this is one of the main
reasons I believe Log4j is a great project to wrap a JNI C++ library.
But to be complete would be expose all the methods not in the book, but
allowable in the Log4J interface, which might require another 1,000 to
2,000 file lines, for things like the hundreds of Appender and Appender
related class methods, and the list for a Release 1.0.  But there's
another reason why this is a good thing to do.  Compare that 1-2,000
lines of C++ wrapper code The Log4J is 32,000 file lines.  Suppose on
the average, any of the ported C++ Log4J's are also 32,000 file lines.
Would you rather maintain 1-2,000 lines of C++ wrapper code that does
everything Log4J does, or 32,000 file lines?  I'll tell you this.  The
current C++ log4j port out on the Log4 website is called log4cxx-0.9.7.
It's published at http://archive.apache.org/dist/logging/log4cxx/. with
a date of 21-May-2004.  Have a go at trying to compile and link it; it's
not easy. I gave up.  I haven't had any luck with any of the others, and
so I started this project of my own.

Now that's the good news, on why you might want to go with a JNI
version, but I agree with you there might be some lurking bad reasons
you might not want to ever call a C++ JNI method to log something, and
you've given me one, but there are probably more.   Oh, here's one of
own: it's a hassle because you have to deal with the JDK.  You have to
go through the hoops of making sure the JDK is installed and on your
path, and you have built using the right jni.h header and compiled and
linked with the right jdk.lib/jdk.dll.  Really, it's a pain. And then
your CLASSPATH get's passed into the runtime of your program, and you
need to be sure that you've added the Log4j jar file to this.  So that's
where good documentation needs to come in.  

Now you are concerned about the performance and you make some good
points, but I think there are some ways to deal with the issues you
brought up. Now when the Log4J logger doesn't need to make the method
call, perhaps because LEVEL is as high as WARN say, it may be checking
for the LEVEL, or perhaps it uses (I'm guessing here) the Abstract
Factory pattern to make the method a stubbed out one, so the call will
be optimized out at run time.  That's a cute one, I've used it before
and the stubbed out method calls really don't get called.  The thing is
however, it seems to me that we can re-create this kind of behavior on
the C++ side.  First, if it's a parameter test issue, say if (LEVEL ==
DEBUG)...., we can get that parameter through the JNI interface and
store it in a private field of the wrapper class, and don't call the JNI
method if the test doesn't pass.  Or, if it's more like the Abstract
Factory pattern kind of thing, though I doubt it is cause this would be
so fancy, but just for grins, we could pull those classes across through
JNI calls, and either make the JNI calls on the C++ side, and test for
speed of such a call to see if the call is optimized out, or add code to
do something like a fancier Abstract Factory "Wrapper", which would
optimize out on the C++ side, or simply create a new instance variable
and don't make the call.  Now for the JNI calls that are going to do
some work, the majority are going to be Logging calls.  These calls send
message to Appender's, which spend most of their time talking across
some kind of a connection until the message is delivered.   For example,
when sending a message to a RollingFileAppender, the Appender (really,
the process) is waiting for the disk to come back and say "done, I wrote
the message, and you can have control back".  And the time from the JNI
call, to the Appender talking to the disk, and the disk returning, and
the JNI method returning, is a lot longer than the time taken up just by
the JNI call across the interface and the JNI return across the
interface.  That's probably true regardless of the Appender type.  The
connection time to send the message always hogs the time.

If we did pursue word on this, here are some tasks that could be added
to a 1 st release:

*	Expose the interface to its fullest.  There are hundred of
public methods that I found related to the Appender classes, etcetera.
You javap them to get the JNI signature and just write the JNI stuff.
Pretty chug-work.  Not time consuming.  Perhaps some portion of these
need do not need to be exposed.  
*	Support exceptions for Methods through the interface.  
*	Support the instantiation of all JDK versions, and test the
CLASSPATH handling.  
*	Port to other other compilers.  Currently supported as
"nearly-POSIX-C++" on Visual Studio 2005  
*	Port to other machine types, and platforms, including CYGWIN.  
*	Write install and interface documentation.  

Cheers,
Gerard

Gerard Dion, Ph.D.
Senior Project Engineer
Simulation and Modeling Group, D621
Northrop Grumman Space Technology
Mail Stop: R4/1023, One Space Park
Redondo Beach, CA, 90278
310/812-8544
gerard.dion@ngc.com


-----Original Message-----
From: Curt Arnold [mailto:carnold@apache.org] 
Sent: Monday, February 05, 2007 9:33 PM
To: Log4J Developers List
Subject: Re: Anyone Interested in a New C++ Log4J?


On Feb 5, 2007, at 6:39 PM, Dion, Gerard (Space Technology) wrote:

> Log4J Users:
>
> I exposed the Java Log4J in C++ by Wrapping it with JNI.  The  
> interface is C++-ish, not JNI-ish; all JNI jobjects and such  
> returned on JNI calls and such, like the org.apache.log4j.Logger,  
> are wrapped and saved inside my own C++ class instances.  So one  
> gets instead an org::apache::log4j::Logger instance for each new  
> call to the Logger interface.  Example:
>
>
> static org::apache::log4j::Logger * newLogger =  
> org::apache::Logger::getLogger(std::string loggerName);
>
>
> How do I publish this new C++ Log4J Wrapper?  It's only 250 lines  
> of code right now, and yet it handles most of the functionality we  
> need.  To support all the functionality of the underlying 32,000  
> lines of Java log4j code, I believe another 1000 lines of C++ might  
> be required, at most.

 From reading the code, it would appear that you would incur a JNI  
call even when processing log requests that don't satisfy the  
threshold.  Both log4j and log4cxx attempt to make discarded logging  
requests as cheap as possible so you can feel free to place debug()  
calls all over your code without worrying about compromising  
performance.

Could you discuss the motivations behind the your effort?

What would be the relative benefits or disadvantages of:

1. Trying to mimic the log4cxx API
2. Provide an adapter that would allow log4j appenders to be used in  
log4cxx.

---------------------------------------------------------------------
To unsubscribe, e-mail: log4j-dev-unsubscribe@logging.apache.org
For additional commands, e-mail: log4j-dev-help@logging.apache.org


Re: Anyone Interested in a New C++ Log4J?

Posted by Curt Arnold <ca...@apache.org>.
On Feb 5, 2007, at 6:39 PM, Dion, Gerard (Space Technology) wrote:

> Log4J Users:
>
> I exposed the Java Log4J in C++ by Wrapping it with JNI.  The  
> interface is C++-ish, not JNI-ish; all JNI jobjects and such  
> returned on JNI calls and such, like the org.apache.log4j.Logger,  
> are wrapped and saved inside my own C++ class instances.  So one  
> gets instead an org::apache::log4j::Logger instance for each new  
> call to the Logger interface.  Example:
>
>
> static org::apache::log4j::Logger * newLogger =  
> org::apache::Logger::getLogger(std::string loggerName);
>
>
> How do I publish this new C++ Log4J Wrapper?  It’s only 250 lines  
> of code right now, and yet it handles most of the functionality we  
> need.  To support all the functionality of the underlying 32,000  
> lines of Java log4j code, I believe another 1000 lines of C++ might  
> be required, at most.

 From reading the code, it would appear that you would incur a JNI  
call even when processing log requests that don't satisfy the  
threshold.  Both log4j and log4cxx attempt to make discarded logging  
requests as cheap as possible so you can feel free to place debug()  
calls all over your code without worrying about compromising  
performance.

Could you discuss the motivations behind the your effort?

What would be the relative benefits or disadvantages of:

1. Trying to mimic the log4cxx API
2. Provide an adapter that would allow log4j appenders to be used in  
log4cxx.

---------------------------------------------------------------------
To unsubscribe, e-mail: log4j-dev-unsubscribe@logging.apache.org
For additional commands, e-mail: log4j-dev-help@logging.apache.org