日志子系统 AMQP Appender
该框架为一些流行的日志子系统提供了日志 appender:
- logback(自 Spring AMQP 1.4 版起)
- log4j2(自 Spring AMQP 1.6 版起)
这些 appender 通过日志子系统的常规机制进行配置,可用属性在以下各节中说明。
常用属性
以下属性对所有 appender 都可用:
表 1. 常用 Appender 属性
| 属性 | 默认值 | 描述 |
|---|---|---|
| exchangeName | logs | 要发布日志事件的 exchange 名称。 |
| exchangeType | topic | 要发布日志事件的 exchange 类型——仅当 appender 声明该 exchange 时才需要。参见 declareExchange。 |
| routingKeyPattern | %c.%p | 用于生成 routing key 的日志子系统 pattern 格式。 |
| applicationId | Application ID——如果 pattern 包含 %X{applicationId},则将其添加到 routing key。 | |
| senderPoolSize | 2 | 用于发布日志事件的线程数。 |
| maxSenderRetries | 30 | 当 broker 不可用或出现其他错误时,重试发送消息的次数。重试延迟如下:N ^ log(N),其中 N 是重试次数。 |
| addresses | 以以下形式给出的、逗号分隔的 broker 地址列表:host:port[,host:port]*——覆盖 host 和 port。 | |
| host | localhost | 要连接的 RabbitMQ host。 |
| port | 5672 | 要连接的 RabbitMQ 端口。 |
| virtualHost | / | 要连接的 RabbitMQ virtual host。 |
| username | guest | 连接时使用的 RabbitMQ 用户。 |
| password | guest | 该用户的 RabbitMQ 密码。 |
| useSsl | false | 是否对 RabbitMQ 连接使用 SSL。参见 RabbitConnectionFactoryBean 与配置 SSL |
| verifyHostname | true | 为 TLS 连接启用服务器主机名验证。参见 RabbitConnectionFactoryBean 与配置 SSL |
| sslAlgorithm | null | 要使用的 SSL 算法。 |
| sslPropertiesLocation | null | SSL 属性文件的位置。 |
| keyStore | null | keystore 的位置。 |
| keyStorePassphrase | null | keystore 的口令。 |
| keyStoreType | JKS | keystore 类型。 |
| trustStore | null | truststore 的位置。 |
| trustStorePassphrase | null | truststore 的口令。 |
| trustStoreType | JKS | truststore 类型。 |
| saslConfig | null (RabbitMQ client default applies) | saslConfig——有效值参见 RabbitUtils.stringToSaslConfig 的 javadoc。 |
| contentType | text/plain | 日志消息的 content-type 属性。 |
| contentEncoding | 日志消息的 content-encoding 属性。 | |
| declareExchange | false | 此 appender 启动时是否声明所配置的 exchange。另见 durable 和 autoDelete。 |
| durable | true | 当 declareExchange 为 true 时,durable 标志被设置为此值。 |
| autoDelete | false | 当 declareExchange 为 true 时,auto-delete 标志被设置为此值。 |
| charset | null | 将 String 转换为 byte[] 时使用的字符集。默认值:null(使用系统默认字符集)。如果当前平台不支持该字符集,则回退到使用系统字符集。 |
| deliveryMode | PERSISTENT | PERSISTENT 或 NON_PERSISTENT,用于决定 RabbitMQ 是否持久化消息。 |
| generateId | false | 用于决定是否将 messageId 属性设置为唯一值。 |
| clientConnectionProperties | null | 逗号分隔的 key:value 键值对列表,用于为 RabbitMQ 连接设置自定义 client 属性。 |
| addMdcAsHeaders | true | 在引入此属性之前,MDC 属性总是会被添加到 RabbitMQ 消息头中。这对于大的 MDC 可能导致问题,因为 RabbitMQ 对所有消息头的缓冲区大小有限,而且这个缓冲区相当小。引入此属性是为了在大 MDC 的情况下避免问题。为向后兼容,此值默认为 true。设为 false 会关闭将 MDC 序列化到消息头。请注意,JsonLayout 默认会将 MDC 添加到消息中。 |
Log4j 2 Appender
以下示例展示如何配置 Log4j 2 appender:
<Appenders>
...
<RabbitMQ name="rabbitmq"
addresses="foo:5672,bar:5672" user="guest" password="guest" virtualHost="/"
exchange="log4j2" exchangeType="topic" declareExchange="true" durable="true" autoDelete="false"
applicationId="myAppId" routingKeyPattern="%X{applicationId}.%c.%p"
contentType="text/plain" contentEncoding="UTF-8" generateId="true" deliveryMode="NON_PERSISTENT"
charset="UTF-8"
senderPoolSize="3" maxSenderRetries="5"
addMdcAsHeaders="false">
</RabbitMQ>
</Appenders>
从 1.6.10 和 1.7.3 版本开始,默认情况下,log4j2 appender 在调用线程上将消息发布到 RabbitMQ。这是因为 Log4j 2 默认不创建线程安全的事件。如果 broker 宕机,则使用 maxSenderRetries 进行重试,重试之间没有延迟。如果你希望恢复此前在独立线程(senderPoolSize)上发布消息的行为,可以将 async 属性设为 true。但是,你还需要配置 Log4j 2 使用 DefaultLogEventFactory 而不是 ReusableLogEventFactory。一种做法是设置系统属性 -Dlog4j2.enable.threadlocals=false。如果你在 ReusableLogEventFactory 下使用异步发布,事件很可能因串扰(cross-talk)而损坏。
Logback Appender
以下示例展示如何配置 logback appender:
<appender name="AMQP" class="org.springframework.amqp.rabbit.logback.AmqpAppender">
<layout>
<pattern><![CDATA[ %d %p %t [%c] - <%m>%n ]]></pattern>
</layout>
<addresses>foo:5672,bar:5672</addresses>
<abbreviation>36</abbreviation>
<includeCallerData>false</includeCallerData>
<applicationId>myApplication</applicationId>
<routingKeyPattern>%property{applicationId}.%c.%p</routingKeyPattern>
<generateId>true</generateId>
<charset>UTF-8</charset>
<durable>false</durable>
<deliveryMode>NON_PERSISTENT</deliveryMode>
<declareExchange>true</declareExchange>
<addMdcAsHeaders>false</addMdcAsHeaders>
</appender>
从 1.7.1 版本开始,Logback 的 AmqpAppender 提供了 includeCallerData 选项,默认为 false。提取调用者数据可能相当昂贵,因为日志事件必须创建一个 throwable 并检查它以确定调用位置。因此,默认情况下,当事件被加入事件队列时,不会提取与该事件关联的调用者数据。你可以通过将 includeCallerData 属性设为 true 来配置 appender 包含调用者数据。
从 2.0.0 版本开始,Logback 的 AmqpAppender 通过 encoder 选项支持 Logback 编码器。encoder 和 layout 选项互斥。
自定义消息
默认情况下,AMQP appender 会填充以下消息属性:
deliveryMode- contentType
contentEncoding(如果配置)messageId(如果配置了generateId)- 日志事件的
timestamp appId(如果配置了 applicationId)
此外,它们还会用以下值填充消息头:
- 日志事件的
categoryName - 日志事件的级别
thread:发生日志事件的线程名称- 日志事件调用的堆栈跟踪位置
- 所有 MDC 属性的副本(除非
addMdcAsHeaders设为false)
每个 appender 都可以被子类化,从而让你在发布之前修改消息。以下示例展示如何自定义日志消息:
public class MyEnhancedAppender extends AmqpAppender {
@Override
public Message postProcessMessageBeforeSend(Message message, Event event) {
message.getMessageProperties().setHeader("foo", "bar");
return message;
}
}
从 2.2.4 开始,log4j2 的 AmqpAppender 可以使用 @PluginBuilderFactory 并同时扩展 AmqpAppender.Builder 来进行扩展。
@Plugin(name = "MyEnhancedAppender", category = "Core", elementType = "appender", printObject = true)
public class MyEnhancedAppender extends AmqpAppender {
public MyEnhancedAppender(String name, Filter filter, Layout<? extends Serializable> layout,
boolean ignoreExceptions, AmqpManager manager, BlockingQueue<Event> eventQueue, String foo, String bar) {
super(name, filter, layout, ignoreExceptions, manager, eventQueue);
@Override
public Message postProcessMessageBeforeSend(Message message, Event event) {
message.getMessageProperties().setHeader("foo", "bar");
return message;
}
@PluginBuilderFactory
public static Builder newBuilder() {
return new Builder();
}
protected static class Builder extends AmqpAppender.Builder {
@Override
protected AmqpAppender buildInstance(String name, Filter filter, Layout<? extends Serializable> layout,
boolean ignoreExceptions, AmqpManager manager, BlockingQueue<Event> eventQueue) {
return new MyEnhancedAppender(name, filter, layout, ignoreExceptions, manager, eventQueue);
}
}
}
自定义 Client 属性
你可以通过添加字符串属性或更复杂的属性来添加自定义 client 属性。
简单字符串属性
每个 appender 都支持向 RabbitMQ 连接添加 client 属性。
以下示例展示如何添加自定义 client 属性:
- logback
- log4j2
<appender name="AMQP" ...>
...
<clientConnectionProperties>thing1:thing2,cat:hat</clientConnectionProperties>
...
</appender>
<Appenders>
...
<RabbitMQ name="rabbitmq"
...
clientConnectionProperties="thing1:thing2,cat:hat"
...
</RabbitMQ>
</Appenders>
这些属性是逗号分隔的 key:value 键值对列表。键和值不能包含逗号或冒号。
查看连接时,这些属性会显示在 RabbitMQ 管理 UI 上。
Logback 的高级技巧
你可以子类化 Logback appender。这样做可以让你在连接建立之前修改 client 连接属性。以下示例展示如何这样做:
public class MyEnhancedAppender extends AmqpAppender {
private String thing1;
@Override
protected void updateConnectionClientProperties(Map<String, Object> clientProperties) {
clientProperties.put("thing1", this.thing1);
}
public void setThing1(String thing1) {
this.thing1 = thing1;
}
}
然后你可以将 <thing1>thing2</thing1> 添加到 logback.xml 中。
对于前面示例中所示的字符串属性,可以使用前述技巧。子类允许添加更丰富的属性(例如添加 Map 或数值属性)。
提供自定义队列实现
AmqpAppender 使用 BlockingQueue 异步地将日志事件发布到 RabbitMQ。默认使用 LinkedBlockingQueue。不过,你可以提供任意类型的自定义 BlockingQueue 实现。
以下示例展示如何为 Logback 这样做:
public class MyEnhancedAppender extends AmqpAppender {
@Override
protected BlockingQueue<Event> createEventQueue() {
return new ArrayBlockingQueue();
}
}
Log4j 2 appender 支持使用 BlockingQueueFactory,如下例所示:
<Appenders>
...
<RabbitMQ name="rabbitmq"
bufferSize="10" ... >
<ArrayBlockingQueue/>
</RabbitMQ>
</Appenders>