java c++ jni log4j swig

java - ¿Integrar log4j con SWIG/JNI?



c++ (1)

Hay tres problemas que debe resolver para obtener C ++ (llamado a través de SWIG generados envoltorios) de inicio de sesión a log4j:

  1. Cómo capturamos la salida C ++ (es decir, enganchar los objetos iostream).
  2. Cómo insertamos este enganche en el contenedor generado.
  3. Cómo llamamos realmente a log4j con las cosas que hemos capturado.

Al responder esta pregunta, escribí el siguiente archivo de encabezado para demostrar cómo funciona mi solución:

#include <iostream> void test1() { std::cout << "OUT: " << "test1/n"; std::cerr << "ERR: " << "test1/n"; } struct HelloWorld { static void test2() { std::cout << "OUT: " << "test2/n"; std::cerr << "ERR: " << "test2/n"; } void test3() const { std::cout << "OUT: " << "test3/n"; std::cerr << "ERR: " << "test3/n"; } };

Al final de esto, quería ver que std::cout y std::cerr vayan a log4j en el orden correcto. He respondido esa pregunta antes , en esta instancia para mantenerla simple y portátil Empecé usando rdbuf() para intercambiar el búfer interno utilizado por std::cout y std::cerr por uno que realmente había creado dentro de una std::stringstream , algo así como:

std::stringstream out; // Capture into this // Save state so we can restore it auto old_buf = std::cout.rdbuf(); // Swap buffer on cout std::cout.rdbuf(out.rdbuf()); // Do the real call to C++ here // ... // Reset things std::cout.rdbuf(old_buf); // Walk through what we captured in out

Por supuesto, esto no capturará el resultado de las funciones de libc ( printf() etc.) ni de las llamadas al sistema ( write() etc.), pero obtendrá toda su salida estándar de C ++.

Entonces ese es el problema # 1 tachado de nuestra lista. Para el problema # 2, SWIG tiene la directiva %exception que coincide con lo que queremos hacer muy bien, nos da la oportunidad de ejecutar código C ++ antes y después de que se despache una llamada a una función envuelta. En el ejemplo anterior, todo lo que tenemos que hacer es usar la variable especial $action para que la sustitución se produzca cuando el comentario "haga la llamada real a C ++ aquí".

Para el problema n. ° 3 necesitamos hacer algunas llamadas Java. Empecé pensando que JNI no sería tan malo, tal vez un poco detallado. Básicamente, todo lo que queremos hacer es duplicar el siguiente código Java ( de log4j documentos ):

import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class HelloWorld { private static final Logger logger = LogManager.getLogger("HelloWorld"); public static void main(String[] args) { logger.info("Hello, World!"); } }

pero dentro de JNI en lugar de Java y pasando la cadena derecha a la llamada getLogger .

Así que juntando todo esto en una interfaz SWIG, obtenemos:

%module test %{ #include "test.hh" #include <sstream> #include <cassert> static const char *defaultLogname="$module"; // Use if we''re not in a class %} // Exception handling for all wrapped calls %exception { // Hook output into this: std::stringstream out; // Save old states auto old_outbuf = std::cout.rdbuf(); auto old_errbuf = std::cerr.rdbuf(); // Do actual buffer switch std::cout.rdbuf(out.rdbuf()); std::cerr.rdbuf(out.rdbuf()); try { $action } catch (...) { // TODO: use RAII instead of poor finally substitute? std::cout.rdbuf(old_outbuf); std::cerr.rdbuf(old_errbuf); throw; } // Restore state std::cout.rdbuf(old_outbuf); std::cerr.rdbuf(old_errbuf); // JNI calls to find mid and instance for Logger.error(String) for the right name static const std::string class_name = "$parentclassname"; // prepare static call to org.apache.logging.log4j.LogManager.getLogger(String) // start with class lookup: jclass logmanagercls = JCALL1(FindClass, jenv, "org/apache/logging/log4j/LogManager"); assert(logmanagercls); // find method ID for right overload of getLogger jmethodID getloggermid = JCALL3(GetStaticMethodID, jenv, logmanagercls, "getLogger", "(Ljava/lang/String;)Lorg/apache/logging/log4j/Logger;"); assert(getloggermid); // Prep name strign to pass into getLogger jstring logname = JCALL1(NewStringUTF, jenv, (class_name.size() ? class_name.c_str(): defaultLogname)); // Actually get the Logger instance for us to use jobject logger = JCALL3(CallStaticObjectMethod, jenv, logmanagercls, getloggermid, logname); assert(logger); // Lookup .error() method ID on logger, we need the jclass to start jclass loggercls = JCALL1(GetObjectClass, jenv, logger); assert(loggercls); // and the method ID of the right overload jmethodID errormid = JCALL3(GetMethodID, jenv, loggercls, "error", "(Ljava/lang/String;)V"); assert(errormid); // Loop over all the lines we got from C++: std::string msg; while(std::getline(out, msg)) { // Pass string into Java logger jstring jmsg = JCALL1(NewStringUTF, jenv, msg.c_str()); JCALL3(CallVoidMethod, jenv, logger, errormid, jmsg); } } // And of course actually wrap our test header %include "test.hh"

Añadí algo de Java para probar que esto funciona:

public class run { public static void main(String[] argv) { System.loadLibrary("test"); test.test1(); HelloWorld.test2(); HelloWorld h1 = new HelloWorld(); h1.test3(); } }

Compilado y ejecutado con log4j 2.6 jar en el directorio actual:

swig3.0 -c++ -java -Wall test.i javac *.java g++ -std=c++1y -Wall -Wextra -shared -o libtest.so test_wrap.cxx -I/usr/lib/jvm/default-java/include/ -I/usr/lib/jvm/default-java/include/linux -fPIC LD_LIBRARY_PATH=. java -classpath log4j-api-2.6.jar:log4j-core-2.6.jar:. run

Cuando ejecuta da:

ERROR StatusLogger No log4j2 configuration file found. Using default configuration: logging only errors to the console. 22:31:08.383 [main] ERROR test - OUT: test1 22:31:08.384 [main] ERROR test - ERR: test1 22:31:08.386 [main] ERROR HelloWorld - OUT: test2 22:31:08.386 [main] ERROR HelloWorld - ERR: test2 22:31:08.386 [main] ERROR HelloWorld - OUT: test3 22:31:08.386 [main] ERROR HelloWorld - ERR: test3

Puntos de discusión:

  • Es el JNI detallado? Definitivamente sí, pero ¿es eso lo suficientemente malo como para ir por otro camino? Para mí no (aunque la idea de los directores es viable, no es tan simple como parece debido a las complejidades con la producción de interfaces )
  • Las indicaciones de fecha y hora de los eventos de registro serán "incorrectas" porque toda la información de registro que se captura se eliminará después de que la llamada de función C ++ subyacente regrese, no durante su ejecución.
  • Los mensajes cout / cerr se mezclan y registran en el mismo nivel. Hice esto debido al punto anterior, si no estuvieran entremezclados de esta manera, conseguir el orden relativo sería imposible sin más trabajo
  • Si sus métodos C ++ producen grandes cantidades de salida, el buffer crecerá bastante grande
  • Si tienes un gran proyecto de C ++, espero que tengas un mejor marco de trabajo en funcionamiento que solo iostreams. Si ese es el caso, probablemente sea mejor que implementes su (s) interfaz (es) de salida con un adaptador que pase directamente a Java con marcas de tiempo, nivel de registro, etc., información retenida. (O viceversa)
  • Incluso al seguir con iostream la biblioteca Boost iostreams hace que sea más fácil 1 escribir algo que realmente enganche las escrituras de salida en el momento en que ocurren en lugar de la técnica de enganche actual (pero introduce dependencias más grandes, por lo que es una compensación). También podría hacerlo sin impulso, pero la clase basic_streambuf es bastante difícil de manejar.

1 Probablemente pueda ilustrar la versión de impulso en esta respuesta si está interesado.

Estoy trabajando en un programa Java que tiene un módulo C ++. Me gustaría que la stdout / stderr de mi C ++ se envíe a un registrador slf4j / log4j.

Algunas posibilidades:

  1. Haga que mi módulo C ++ capture stdout / stderr en una cadena que se devuelve y se envía a un registrador. Una desventaja es que el módulo C ++ puede ser costoso y esto sería asincrónico.
  2. Haga que mi software Java cree / administre un archivo temporal al cual el módulo C ++ puede escribir. Al finalizar, mi software leería y luego eliminaría el archivo y enviaría los datos a slf4j.
  3. ¿Hay alguna forma de obtener un ostream de log4j que podría pasar a través de mi interfaz swig?
  4. parece que podría crear un Director en swig para pasar una cadena a Java desde C ++ para reenviar al registrador.

¿Cómo han resuelto otras personas este problema?