使用log4j2编写自定义json消息的最佳方法

时间:2016-09-22 19:59:41

标签: json log4j2

我一直在使用log4j进行不同类型的项目,并且对log4j2有一些经验。所有实现都使用默认的appender和布局。目前我需要编写一个以json格式写入的应用程序。所以我通过设置一个非常简单的log4j2记录器来尝试log4j2 JSONLayout布局。

public class JSONLogger {

    private static final Logger LOGGER = LogManager.getLogger();

    public static void main(String[] args) {
        JSONLogger jsonlogger = new JSONLogger() ;
    }

    public JSONLogger() {
        LOGGER.log(Level.FATAL, "hi mum!") ;

         int val1 = 10, val2 = 11, val3 = 12;

         LOGGER.log(Level.FATAL,"val1={}, val2={}, val3={}", val1, val2, val3);
    }

}

jsonLoggerProperties.xml

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="DEBUG">
    <Properties>
        <Property name="log-path">/Users/petervannes/NetBeansProjects/JSONLogger/logfiles</Property>
    </Properties>

    <Appenders>
        <RollingFile name="json_file_appender" fileName="${log-path}/jsonlogger.json"
                     filePattern="${log-path}/%d{yyyyMMdd}_jsonlogger-%i.json" >
            <JSONLayout complete="true" compact="false"/>
            <Policies>
                <SizeBasedTriggeringPolicy size="1 KB" />
            </Policies>
            <DefaultRolloverStrategy max="4"/>
        </RollingFile>

    </Appenders>


    <Loggers>
        <root level="debug" additivity="false">
            <AppenderRef ref="json_file_appender"/>
        </root>
    </Loggers>
</Configuration>

导致类似于;

的日志条目
, {
  "timeMillis" : 1474573600359,
  "thread" : "main",
  "level" : "FATAL",
  "loggerName" : "JSONLogger",
  "message" : "val1=10, val2=11, val3=12",
  "contextStack" : [ "fieldName2" ],
  "endOfBatch" : false,
  "loggerFqcn" : "org.apache.logging.log4j.spi.AbstractLogger",
  "threadId" : 1,
  "threadPriority" : 5
}

我需要的是记录这样的JSON格式;

, {
  "DateTime" : "08/01/2016 21:33:22.334",
  "level" : "FATAL",
  "Summary" : "Something has gone wrong",
  "ChainManager" : "Manager A",
  "Optionals" : { "Key_1": "Value1",
                  "Key_2": "Value2" }
}

这可能与log4j2 JSONLayout有关,还是我可以使用任何其他布局来获取此格式?

4 个答案:

答案 0 :(得分:3)

问题是如何使用log4j2编写自定义json消息。

从log4j2版本的2.11版开始,这是可能的:

https://issues.apache.org/jira/browse/LOG4J2-2190

JSONLayout的新参数称为

  

objectMessageAsJsonObject

。 示例项目文件;

<强> log4j2.properties

status = error


appender.ana_whitespace.type = RollingFile
appender.ana_whitespace.name = ana_whitespace
appender.ana_whitespace.fileName = ${sys:es.logs.base_path:-target}${sys:file.separator}ana_whitespace.log
appender.ana_whitespace.layout.type = JsonLayout
appender.ana_whitespace.layout.propertiesAsList = false
appender.ana_whitespace.layout.compact = false
appender.ana_whitespace.layout.eventEol = true
appender.ana_whitespace.layout.objectMessageAsJsonObject = true
appender.ana_whitespace.layout.complete= true
appender.ana_whitespace.layout.properties= true
appender.ana_whitespace.filePattern = ${sys:es.logs.base_path:-target}${sys:file.separator}ana_whitespace-%d{yyyy-MM-dd}.log
appender.ana_whitespace.filter.1.type = MarkerFilter
appender.ana_whitespace.filter.1.onMismatch=DENY
appender.ana_whitespace.filter.1.onMatch=ACCEPT
appender.ana_whitespace.filter.1.marker=ANA_WHITESPACE
appender.ana_whitespace.policies.type = Policies
appender.ana_whitespace.policies.time.type = TimeBasedTriggeringPolicy
appender.ana_whitespace.policies.time.interval = 1
appender.ana_whitespace.policies.time.modulate = true
appender.ana_whitespace.policies.size.type = SizeBasedTriggeringPolicy
appender.ana_whitespace.policies.size.size = 10 MB

rootLogger.level = info
rootLogger.appenderRef.ana_whitespace.ref = ana_whitespace

示例Java代码

package de.es.stemmer;

import java.io.IOException;
import java.util.Map;
import java.util.TreeMap;

import org.apache.http.client.ClientProtocolException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
import org.apache.logging.log4j.ThreadContext;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.message.ObjectMessage;

public class JsonLoggerTest {
    final static Logger log = LogManager.getLogger(JsonLoggerTest.class);
    final static Marker MARKER_WHITESPACE = MarkerManager.getMarker("ANA_WHITESPACE");

    public static void main(String[] args) throws ClientProtocolException, IOException {
        System.setProperty("es.logs.base_path", "target");
        System.setProperty("es.logs.cluster_name", "_cluster");
        LoggerContext.getContext().reconfigure();
        ThreadContext.put("orig", "MDC_origValue");
        ThreadContext.put("source", "MDC_sourceSnippet");
        Map<String, String> map = new TreeMap<>();
        map.put("orig", "msg_origValue");
        map.put("source", "msg_sourceSnippet");
        ObjectMessage msg = new ObjectMessage(map);
        log.info(MARKER_WHITESPACE, msg);
        ThreadContext.remove("orig");
        ThreadContext.remove("source");
    }

}

JSON日志条目

[
{
  "thread" : "main",
  "level" : "INFO",
  "loggerName" : "de.es.stemmer.JsonLoggerTest",
  "marker" : {
    "name" : "ANA_WHITESPACE"
  },
  "message" : {
    "orig" : "msg_origValue",
    "source" : "msg_sourceSnippet"
  },
  "endOfBatch" : false,
  "loggerFqcn" : "org.apache.logging.log4j.spi.AbstractLogger",
  "instant" : {
    "epochSecond" : 1526576578,
    "nanoOfSecond" : 184000000
  },
  "contextMap" : {
    "orig" : "MDC_origValue",
    "source" : "MDC_sourceSnippet"
  },
  "threadId" : 1,
  "threadPriority" : 5
}

]

答案 1 :(得分:3)

如果您正在寻找一种生成自定义JSON日志消息的方法,而没有log4j2添加的任何“噪声”,则可以创建Message接口的实现并使用其他布局-例如{{ 1}}。下面是这种方法的示例:

第一个实现PatternLayout接口的类

Message

接下来是log4j2.xml配置:

import java.util.HashMap;
import java.util.Map;

import org.apache.logging.log4j.message.Message;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class JSONMessage implements Message {

    private static final long serialVersionUID = 538439258494853324L;
    private String messageString;
    private static final Gson GSON = new GsonBuilder()
            .setPrettyPrinting()
            .create();

    public JSONMessage(){
        this(null);
    }

    public JSONMessage(Object msgObj){
        parseMessageAsJson(msgObj);
    }

    public JSONMessage(String msgStr){
        Map<String,String> msgObj = new HashMap<>();
        msgObj.put("message", msgStr);
        parseMessageAsJson(msgObj);
    }

    private void parseMessageAsJson(Object msgObj){
        messageString = GSON.toJson(msgObj);
    }

    @Override
    public String getFormattedMessage() {
        return messageString;
    }

    @Override
    public String getFormat() {
        return messageString;
    }

    @Override
    public Object[] getParameters() {
        return null;
    }

    @Override
    public Throwable getThrowable() {
        return null;
    }

}

现在有了一个简单的应用程序类来生成日志事件

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" name="App">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
                <PatternLayout pattern="%m%n" />
        </Console>
    </Appenders>

    <Loggers>
        <Root level="trace">
            <AppenderRef ref="Console" />
        </Root>
    </Loggers>
</Configuration>

以下是一些示例输出:

import java.util.HashMap;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;


public class App {

    private static final Logger logger = LogManager.getLogger();

    public static void main( String[] args )
    {
        HashMap<String,Object> msgMap = new HashMap<>();
        msgMap.put("someInt", 123);
        msgMap.put("note", "Maybe you put a message here");

        HashMap<String,Object> anotherMap = new HashMap<>();
        anotherMap.put("key1", "value1");
        anotherMap.put("key2", "value2");
        msgMap.put("map", anotherMap);
        logger.info(new JSONMessage(msgMap));
    }
}

请注意,我正在使用Gson创建JSON输出,但是您可以使用任何想要的库。还请注意,此代码不会生成“完整的” JSON,因为它不会在日志的开头和结尾或消息对象之间的逗号之间添加方括号。

答案 2 :(得分:2)

我找到了适合我的解决方案; slf4j-json-logger。 它是一个slf4j框架,因此应该包含在pom.xml中。 示例项目文件;

<强>的pom.xml

  <?xml version="1.0" encoding="UTF-8"?>
  <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <groupId>com.reddipped</groupId>
     <artifactId>JSONLogger_2</artifactId>
     <version>1.0-SNAPSHOT</version>
     <packaging>jar</packaging>
     <properties>

        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.6</maven.compiler.source>
        <maven.compiler.target>1.6</maven.compiler.target>

        <mainClass>com.reddipped.jsonlogger_2.Test</mainClass>

        <slf4j.version>1.7.21</slf4j.version>
        <!-- current log4j 2 release -->
        <log4j.version>2.6.2</log4j.version> 

     </properties>

     <dependencies>
        <dependency>
           <groupId>org.slf4j</groupId>
           <artifactId>slf4j-api</artifactId>
           <version>${slf4j.version}</version>
        </dependency>
        <!-- Binding for Log4J -->
        <dependency>
           <groupId>org.apache.logging.log4j</groupId>
           <artifactId>log4j-slf4j-impl</artifactId>
           <version>${log4j.version}</version>
        </dependency>
        <!-- Log4j API and Core implementation required for binding -->
        <dependency>
           <groupId>org.apache.logging.log4j</groupId>
           <artifactId>log4j-api</artifactId>
           <version>${log4j.version}</version>
        </dependency>
        <dependency>
           <groupId>org.apache.logging.log4j</groupId>
           <artifactId>log4j-core</artifactId>
           <version>${log4j.version}</version>
        </dependency>
        <!-- Logger slf4j-json-logger -->
        <dependency>
           <groupId>com.savoirtech.logging</groupId>
           <artifactId>slf4j-json-logger</artifactId>
           <version>2.0.2</version>
        </dependency>
     </dependencies> 
  </project>

<强> log4j2.xml

  <?xml version="1.0" encoding="UTF-8"?>

  <!--

  Use java property log4j.configurationFile to specify log4j2.xml location
  if not available in classpath

  -    Dlog4j.configurationFile="/Users/petervannes/NetBeansProjects/JSONLogger_2/src/mann/java/resources/log4j2.xml"

  -->
  <configuration status="trace">
     <Properties>
        <Property name="log-path">/Users/petervannes/NetBeansProjects/JSONLogger_2/logfiles</Property>
     </Properties>
     <appenders>
        <RollingFile name="RollingFile" fileName="${log-path}/jsonlogger.json"
                  filePattern="${log-path}/%d{yyyyMMdd}_jsonlogger-%i.json" >
           <PatternLayout>
              <pattern>%m%n</pattern>
           </PatternLayout> 
           <Policies>
              <TimeBasedTriggeringPolicy />
              <SizeBasedTriggeringPolicy size="1 KB" />
           </Policies>
           <DefaultRolloverStrategy max="4"/>
        </RollingFile>
     </appenders>
     <Loggers>
        <Logger name="JSONLogger" level="debug" additivity="false">
           <AppenderRef ref="RollingFile" />
        </Logger>
        <Root level="debug">
           <AppenderRef ref="RollingFile" />
        </Root>
     </Loggers>
  </configuration>

示例Java代码

  package com.reddipped.jsonlogger_2;

  import com.savoirtech.logging.slf4j.json.LoggerFactory;
  import java.util.HashMap;
  import java.util.Map;

  /**
   *
   * @author petervannes
   */
  public class Test {
      public static void main(String[] args) {   
        Test t = new Test() ;
     }


     public Test() {

        LoggerFactory.setIncludeLoggerName(false);
        LoggerFactory.setDateFormatString("yyyy-MM-dd HH:mm:ss.SSS");

         com.savoirtech.logging.slf4j.json.logger.Logger LOGGER =  LoggerFactory.getLogger("JSONLogger");

     Map<String, String> optionalFields = new HashMap();
     optionalFields.put("CaseNumber", "C12.12343");
     optionalFields.put("Step","Assignment") ;
     optionalFields.put("Department","BPM") ;

     String LOB = "Business Administration" ;
     String Service = "DocumentService" ;
     String Process = "AddAttachements" ;
     String Reason = "Technical" ; 

     LOGGER.error().message("Conversion error 'incompatible PDF document'")
           .field("LOB",LOB)
           .field("Service", Service)
           .field("Process",Process)
           .field("Reason", Reason)
           .map("OptionalFields", optionalFields).log() ;
     }

  }

JSON日志条目

  {
    "message": "Conversion error  'incompatible PDF document'",
    "LOB": "Business Administration",
    "Service": "DocumentService",
    "Process": "AddAttachements",
    "Reason": "Technical",
    "OptionalFields": {
     "Step": "Assignment",
     "Department": "BPM",
     "CaseNumber": "C12.12343"
    },
    "level": "ERROR",
    "thread_name": "main",
    "class": "com.reddipped.jsonlogger_2.Test",
    "@timestamp": "2016-09-23 10:18:06.623"
  }

答案 3 :(得分:0)

我知道这是一个老问题,但我认为有更好的方法。

您应该使用 JSON Template Layout

然后你就可以使用这样的模板来配置你的 JsonLayout:

{
  "mdc": {
    "$resolver": "mdc"
  },
  "exception": {
    "exception_class": {
      "$resolver": "exception",
      "field": "className"
    },
    "exception_message": {
      "$resolver": "exception",
      "field": "message"
    },
    "stacktrace": {
      "$resolver": "exception",
      "field": "stackTrace",
      "stackTrace": {
        "stringified": true
      }
    }
  },
  "line_number": {
    "$resolver": "source",
    "field": "lineNumber"
  },
  "class": {
    "$resolver": "source",
    "field": "className"
  },
  "@version": 1,
  "source_host": "${hostName}",
  "message": {
    "$resolver": "message",
    "stringified": true
  },
  "thread_name": {
    "$resolver": "thread",
    "field": "name"
  },
  "@timestamp": {
    "$resolver": "timestamp"
  },
  "level": {
    "$resolver": "level",
    "field": "name"
  },
  "file": {
    "$resolver": "source",
    "field": "fileName"
  },
  "method": {
    "$resolver": "source",
    "field": "methodName"
  },
  "logger_name": {
    "$resolver": "logger",
    "field": "name"
  }
}

可以使用模板进行许多配置。

在此处查看更多信息:

https://logging.apache.org/log4j/2.x/manual/json-template-layout.html