是否将log4j与SWIG/JNI集成? [英] Integrating log4j with SWIG/JNI?

查看:55
本文介绍了是否将log4j与SWIG/JNI集成?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在研究具有C ++模块的Java程序.我希望将C ++的stdout/stderr发送到slf4j/log4j记录器.

I'm working on a Java program which has a C++ module. I'd like for my C++'s stdout/stderr to be sent to a slf4j/log4j logger.

一些可能性:

  1. 让我的C ++模块以字符串形式捕获stdout/stderr,该字符串将返回并发送到记录器.缺点是C ++模块可能很昂贵,而且可能是异步的.
  2. 让我的Java软件创建/管理一个C ++模块可以写入的临时文件.完成后,我的软件将读取并删除文件,然后将数据发送到slf4j.
  3. 有什么方法可以通过我的swig接口从log4j获得一个ostream吗?
  4. 看起来我可以创建导演,并通过swig传递从C ++到Java的字符串,以转发到记录器.
  1. Have my C++ module capture stdout/stderr in a string which is returned and sent to a logger. A downside is the C++ module might be expensive and this would be asynchronous.
  2. Have my Java software create/manage a temp file to which the C++ module can write. Upon completion, my software would read and then delete the file and send the data to slf4j.
  3. is there some way to get an ostream from log4j I could pass through my swig interface?
  4. it looks like I could create a Director in swig to pass a string to Java from C++ to forward to the logger.

其他人如何解决了这个问题?

How have other people solved this problem?

推荐答案

要使C ++(通过SWIG生成的包装器调用)记录到log4j,需要解决三个问题:

There are three problems you need to solve to get C++ (called via SWIG generated wrappers) logging to log4j:

  1. 我们如何捕获C ++输出(即,钩住iostream对象).
  2. 我们如何将此钩子插入到生成的包装器中.
  3. 我们如何使用捕获的内容实际调用log4j.

在回答这个问题时,我编写了以下头文件来演示我的解决方案的工作原理:

In answering this question I wrote the following header file to demonstrate how my solution works:

#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";
  }
};

最后,我想看看std::coutstd::cerr都以正确的顺序进入log4j.我之前已经回答了这个问题,在这种情况下,为了使其简单易行,我开始使用

At the end of this I wanted to see both std::cout and std::cerr going to log4j in the correct order. I've answered that question before, in this instance to keep it simple and portable I started out using rdbuf() to swap the internal buffer used by std::cout and std::cerr to one that I'd actually created inside a std::stringstream, something like:

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

当然,这不会捕获libc函数(printf()等)或系统调用(write()等)的输出,但是它将获取所有标准C ++输出.

Of course this won't capture output from libc functions (printf() etc.) or system calls (write() etc.) but it will get all your standard C++ output.

这就是问题#1超出了我们的列表.对于问题2,SWIG具有与内容匹配的 %exception指令我们想做得很好,它使我们有机会在分派对包装函数的调用之前和之后执行C ++代码.在上面的示例中,我们要做的就是使用特殊变量$action来使替换发生在注释在此处进行C ++的真正调用"的地方.

So that's problem #1 crossed off our list. For problem #2 SWIG has the %exception directive that matches what we want to do quite nicely, it gives us a chance to execute C++ code before and after a call into a wrapped function gets dispatched. In the above example all we need to do is use the special variable $action to get the substitution to happen where the comment "do the real call to C++ here".

对于问题3,我们需要进行一些Java调用.我开始认为JNI不会太糟,也许有点冗长.基本上,我们要做的就是复制以下Java代码(从log4j文档中 /a>):

For problem #3 we need to make some Java calls happen. I started out thinking that JNI wouldn't be too bad, perhaps a little verbose. Basically all we want to do is duplicate the following Java code (from the log4j docs):

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!");
    }
}

,但在JNI而不是Java内,并将正确的字符串传递给getLogger调用.

but inside JNI rather than Java and passing the right string into the getLogger call.

因此将所有这些放到SWIG界面中,我们得到:

So putting this all together into a SWIG interface we get:

%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"

我添加了一些Java来证明它可行:

I added some Java to prove this works:

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

编译并与当前目录中的log4j 2.6 jar一起运行:

Compiled and ran with log4j 2.6 jars in current directory:

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

运行时给出:

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

讨论要点:

  • JNI是否冗长?肯定是的,但是那足以使它走上另一条路吗?对我而言不是(尽管导演的想法是可行的,但由于制作过程的复杂性,它听起来并不简单)接口)
  • 这里的日志事件时间戳是错误的",因为所有捕获的日志信息将在底层C ++函数调用返回后而不是在执行期间被推出
  • cout/cerr消息混合并记录在同一级别.我之所以这样做是因为前面的观点,如果不像这样将它们混合在一起,那么就不进行更多工作就不可能获得相对的顺序
  • 如果您的C ++方法产生大量输出,则缓冲区将变得相当大
  • 如果您有一个大型C ++项目,希望与iostreams一起使用更好的日志记录框架.如果是这种情况,您最好使用适配器直接实现Java输出,并保留时间戳,日志级别等信息,以实现其输出接口. (反之亦然)
  • 即使坚持使用iostream,Boost iostreams库也使得 1 编写起来比当前的挂钩技术更容易编写实际上会在输出写入时挂钩的东西(但是引入了更大的依赖关系,因此权衡).您也可以不使用boost而做到这一点,但是basic_streambuf类相当笨拙.
  • Is the JNI verbose? Definitely yes, but is that bad enough to go down another road? For me not (although the directors idea is viable it's not as simple as it sounds due to complexities with producing interfaces)
  • The timestamps on your log events will be "wrong" here because all the log information that gets captured will be pushed out after the underlying C++ function call returns, not during its execution
  • The cout/cerr messages get mixed and logged at the same level. I did this because of the previous point, if they weren't intermixed like this getting the relative ordering would be impossible without more work
  • If your C++ methods produce large quantities of output the buffer will grow rather large
  • If you've got a large C++ project I'd hope you had a better logging framework in use than just iostreams. If that's the case you're probably better off implementing its output interface(s) with an adaptor that passes directly to Java with timestamps, log level etc. info retained. (Or vice versa)
  • Even sticking with iostream the Boost iostreams library makes it easier1 to write something that would actually hook the output writes at the point they happen rather than the current hooking technique (but introduce bigger dependencies so it's a trade off). You could also do it without boost, but the basic_streambuf class is fairly unwieldy.

1 如果您有兴趣,我可能可以在此答案中说明增强版本.

1 I can probably illustrate the boost version in this answer if you're interested.

这篇关于是否将log4j与SWIG/JNI集成?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆