5

我是 Grails 的新手,我正在尝试配置 Log4j,以便它记录发生日志调用的确切文件和行。没有模式可以作为conversionPattern!似乎 Grails 以 Log4j 看不到调用的真实来源的方式包装了记录器。

我知道这个线程,但我不确定如何创建自定义附加程序。我简直不敢相信没有人已经开发出一些东西来解决这个问题!

我愿意接受任何建议:

  • 是否在 Grails 中使用 Log4j 以外的东西来获取实际的文件+行(Logback?)?
  • 任何拥有他愿意分享的现有“自定义附加程序”的人?

提前致谢!

4

1 回答 1

8

我实际上是自己做的。我想我应该为它做一个合适的 Grails 插件,但我仍然对 Grails 不够满意,无法确保代码始终有效。我使用 Grails 2.2.4 从 Controller 和 Service 记录日志来测试它,它似乎运行良好。

它通过检查堆栈跟踪来查找发生调用的实际文件和行,然后在MDC线程上下文中添加此信息。添加到的值MDC可以由使用%X{fileAndLine}令牌的(其他)附加程序使用。

这是代码和 javadoc(阅读它!):

package logFileLineInjectorGrailsPlugin

import org.apache.log4j.Appender;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.spi.LoggingEvent;
import org.apache.log4j.Logger;
import java.lang.StackTraceElement;
import org.apache.log4j.MDC;

/**
 * Allows the log appenders to have access to the FILE and LINE where the log call actually occurred.
 * 
 * (1) Add this pseudo appender to your other appenders, in Config.groovy. Then you can use 
 * "%X{fileAndLine}" in the other appenders to output the file and line where the log call actually occurred.
 * 
 * ------------
 * log4j = {
 *     appenders {
 *      appender name:'fileAndLineInjector', new logFileLineInjectorGrailsPlugin.FileAndLineInjector()
 *      // example of a console appender using the "%X{fileAndLine}" token :
 *         console name:'stdout', layout:pattern(conversionPattern: '[%d{yyyy-MM-dd HH:mm:ss}] %-5p ~ %m ~ %c ~ %X{fileAndLine}%n')
 *     }
 *  (...)
 * ------------
 * 
 * (2) Then add it has the *first* appender reference in the declarations of the loggers in which you want to use the "%X{fileAndLine}" token.
 *  
 * For example :
 * 
 * ------------
 * root {
 *     error 'fileAndLineInjector', 'stdout'
 * }
 * ------------
 *  
 * With this setup in place, a call to log.error("test!") will result in something like :
 *  
 * [2013-08-12 19:16:15] ERROR ~ test! ~ grails.app.services.testProject.TestService ~ (TestService.groovy:8)
 * 
 * In Eclipse/STS/GGTS (I didn't try in other IDEs), when "%X{fileAndLine}" is outputed in the internal console, the text is clickable
 * and leads to the actual file/line.
 * 
 *
 */
class FileAndLineInjector extends AppenderSkeleton {

    @Override
    public void close() {
    }

    @Override
    public boolean requiresLayout() {
        return false;
    }

    @Override
    protected void append(LoggingEvent event) {

        StackTraceElement[] strackTraceElements = Thread.currentThread().getStackTrace();

        StackTraceElement targetStackTraceElement = null;
        for(int i = 0; i < strackTraceElements.length; i++) {
            StackTraceElement strackTraceElement = strackTraceElements[i];
            if(strackTraceElement != null &&
               strackTraceElement.declaringClass != null &&
               strackTraceElement.declaringClass.startsWith("org.apache.commons.logging.Log\$") &&
               i < (strackTraceElements.length - 1)) {
                   targetStackTraceElement = strackTraceElements[++i];
                   while(targetStackTraceElement.declaringClass != null &&
                         targetStackTraceElement.declaringClass.startsWith("org.codehaus.groovy.runtime.callsite.") &&
                         i < (strackTraceElements.length - 1)) {
                       targetStackTraceElement = strackTraceElements[++i];
                   }
                   break;
            }
        }

        if(targetStackTraceElement != null) {
            MDC.put("fileAndLine", "(" + targetStackTraceElement.getFileName() + ":" + targetStackTraceElement.getLineNumber() + ")");
        } else {
            MDC.remove("fileAndLine");
        }
    }
}

让我知道是否有不清楚的地方或者您是否找到了改进的方法!

于 2013-08-12T23:43:37.177 回答