Silicon-based Life 赋予硅基生命能力,说简单点就是码农。
structlog4j的介绍
Posted on: 2020-05-10 Edited on: 2020-05-10 In: JAVA工具包 Views: 

JAVA结构化日志包

GITHUB地址

简介

Structlog4J基于SLF4J的API基础之上,设计了生成简而易懂的具有数据结构的日志消息,可以将日志内容提供给一些日志分析的服务,比如说LogStash,Splunk,ElasticSearch,等等。

如何添加到项目中

这个工具包的jar包已经提交到比较流行的Bintray JCcenter Maven 仓库中。

Gradle 方式引用

1
2
3
4
5
6
7
8
9
10
11
repositories {
jcenter()
}

compile 'structlog4j:structlog4j-api:1.0.0'

// Optional JSON formatter
compile 'structlog4j:structlog4j-json:1.0.0'

// Optional YAML formatter
compile 'structlog4j:structlog4j-yaml:1.0.0'

Maven

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!--仓库-->
<repositories>
<repository>
<id>jcenter</id>
<url>http://jcenter.bintray.com</url>
</repository>
</repositories>
<!--依赖-->
<dependency>
<groupId>structlog4j</groupId>
<artifactId>structlog4j-api</artifactId>
<version>1.0.0</version>
<type>pom</type>
</dependency>

<!-- Optional JSON formatter -->
<dependency>
<groupId>structlog4j</groupId>
<artifactId>structlog4j-json</artifactId>
<version>1.0.0</version>
<type>pom</type>
</dependency>

<!-- Optional YAML formatter -->
<dependency>
<groupId>structlog4j</groupId>
<artifactId>structlog4j-yaml</artifactId>
<version>1.0.0</version>
<type>pom</type>
</dependency>

预览

标准的JAVA日志输出像这样

1
Processed 23 flight records for flight UA1234 for airline United

这个是人类比较容易读懂的,但是对于那些日志分析服务是要解析这些内容的含义是比较麻烦的,因为任何开发人员都可以输入任何他们可以表达预期含义的文字和格式。

解决上述问题,我们利用这个工具包可以生成的消息格式像这样

1
Processed flight records recordCount=23 airlineCode=UA flightNumber=1234 airlineName=United

或者是JSON格式(需要使用自带的JSON格式化包)

1
2
3
4
5
6
7
{
"message": "Processed flight records",
"recordCount": 23,
"airlineCode": "UA",
"flightNumber": "1234",
"airlineName": "United"
}

或者是YAML格式(需要使用自带的YAML格式化包)

1
2
3
4
5
message: Processed flight records
recordCount: 23
airlineCode: UA
flightNumber: 1234
airlineName: United

这些格式是非常容易被解析的,通过键值对的形式非常简单准确的描述了日志表达的信息。

When this type of log entry is forwarded to a log aggregation service (such as Splunk, Logstash, etc) it is trivial to parse it and extract context information from it.
Thus, it is very easy to perform log analytics, which are criticial to many open applications (especially multi-tenant cloud applications).

Usage

StructLog4J is implemented itself on top of the SLF4J API. Therefore any application that already uses SLF4J can
start using it immediately as it requires no other changes to existing logging configuration.

Instead of the standard SLF4J Logger, you must instantiate the StructLog4J Logger:

private ILogger log = SLoggerFactory.getLogger(MyClass.class);

The ILogger interface is very simple and offers just these basic methods:

public interface ILogger {
    public void error(String message, Object...params);
    public void warn(String message, Object...params);
    public void info(String message, Object...params);
    public void debug(String message, Object...params);
    public void trace(String message, Object...params);
}

Logging key value pairs

Just pass in key/value pairs as parameters (all keys must be String, values can be anything), e.g.

log.info("Starting processing",
            "user", securityContext.getPrincipal().getName(),
            "tenanId",securityContext.getTenantId());

which would generate a log message like:

Starting processing user=johndoe@gmail.com tenantId=SOME_TENANT_ID

Logging exceptions

There is no separate API for Throwable (like in SLF4j), just pass in the exception as one of the parameters (order is not
important) and we will log its root cause message and the entire exception stack trace:

} catch (Exception e) {
    log.error("Error occcurred during batch processing",
        "user",securityContext.getPrincipal().getName(),
        "tenanId",securityContext.getTenantId(),
        e);
}

which would generate a log message like:

Error occurred during batch processing user=johndoe@gmail.com tenantId=SOME_TENANT_ID errorMessage="ORA-14094: Oracle Hates You"
...followed by regular full stack trace of the exception...

Enforcing custom logging format per object

If you wish, any POJO in your app can implement the IToLog interface, e.g.

public class TenantSecurityContext implements IToLog {

    private String userName;
    private String tenantId;

    @Override
    public Object[] toLog() {
        return new Object[]{"userName",getUserName(),"tenantId",getTenantId()};
    }
}

Then you can just pass in the object instance directly, without the need to specify any key/value pairs:

log.info("Starting processing",securityContext);

and that will generate a log entry like:

Starting processing user=johndoe@gmail.com tenantId=SOME_TENANT_ID

All together now

You can mix and match all of these together without any issues:

    } catch (Exception e) {
        log.error("Error occcurred during batch processing",
            securityContext,
            e,
            "hostname", InetAddress.getLocalHost().getHostName());
    }

and you would get:

Error occurred during batch processing user=johndoe@gmail.com tenantId=SOME_TENANT_ID errorMessage="ORA-14094: Oracle Hates You" hostname=DEV_SERVER1
...followed by regular full stack trace of the exception...

Specifying mandatory context key/value pairs

If you have specific key/value pairs that you would like logged automatically with every log entry (host name and service name are a good example),
then you just have to specify a mandatory context lambda:

StructLog4J.setMandatoryContextSupplier(() -> new Object[]{
    "hostname", InetAddress.getLocalHost().getHostName(),
    "serviceName","MyService"});

Now these mandatory key/value pairs will be logged automatically on every log entry, without the need to specify them manually.

Logging Formats

Key/Value Pairs

By default we log in the standard key/value pair format, e.g.:

Starting processing user=johndoe@gmail.com tenantId=SOME_TENANT_ID

No extra configuration is necesary.

JSON

If you want all messages to be logged in JSON instead, e.g.

    {
        "message": "Started processing",
        "user": johndoe@gmail.com,
        "tenantId": "SOME_TENANT_ID"
    }

then you need to add the JSON formatter library as a dependency (where $version is the current library version):

compile 'structlog4j:structlog4j-json:$version'

and then just execute the following code in the startup main() of your application:

import com.github.structlog4j.json.JsonFormatter;

StructLog4J.setFormatter(JsonFormatter.getInstance());

That’s it.

YAML

If you want all messages to be logged in YAML instead, e.g.

message: Started processing
user: johndoe@gmail.com
tenantId: SOME_TENANT_ID

then you need to add the YAML formatter library as a dependency (where $version is the current library version):

compile 'structlog4j:structlog4j-yaml:$version'

and then just execute the following code in the startup main() of your application:

import com.github.structlog4j.yaml.YamlFormatter;

StructLog4J.setFormatter(YamlFormatter.getInstance());

That’s it.

License

MIT License.

--- 本文结束 The End ---