日志子系统 AMQP 附加器
该框架为一些流行的日志子系统提供了日志附加器(logging appenders):
-
logback(自 Spring AMQP 1.4 版本起)
-
log4j2(自 Spring AMQP 1.6 版本起)
appenders 通过使用日志子系统的常规机制进行配置,可用的属性在以下部分中指定。
通用属性
所有 appender 都支持以下属性:
表 1. 常见的 Appender 属性
属性 | 默认 | 描述 |
---|---|---|
exchangeName | 日志 | 发布日志事件的目标交换机的名称。 |
exchangeType 是 RabbitMQ 中的一个概念,表示交换机的类型。RabbitMQ 提供了多种类型的交换机,每种类型的交换机决定了消息如何被路由到队列。常见的交换机类型包括: |
- direct:消息会被路由到与消息的
routingKey
完全匹配的队列。 - fanout:消息会被路由到所有绑定到该交换机的队列,忽略
routingKey
。 - topic:消息会被路由到与
routingKey
模式匹配的队列。 - headers:消息会被路由到与消息头(headers)匹配的队列。
在代码中,exchangeType
通常用于声明交换机时指定其类型。例如:
channel.exchange_declare(exchange='logs', exchange_type='fanout')
在这个例子中,exchange_type='fanout'
表示声明了一个 fanout
类型的交换机。| 主题| 发布日志事件到哪种类型的 exchange —— 仅在 appender 声明 exchange 时需要。参见 declareExchange
。 |
| routingKeyPattern
| %c.%p| 用于生成路由键的日志子系统模式格式。 |
| applicationId
是 Android 应用中用于唯一标识应用程序的字符串。它在 build.gradle
文件中定义,通常与应用的包名一致。applicationId
用于在 Google Play 商店和其他 Android 设备上区分不同的应用。例如:
android {
defaultConfig {
applicationId "com.example.myapp"
minSdkVersion 21
targetSdkVersion 30
versionCode 1
versionName "1.0"
}
}
在这个例子中,com.example.myapp
就是应用的 applicationId
。| | 应用 ID — 如果模式中包含 %X{applicationId}
,则会将其添加到路由键中。 |
| senderPoolSize
| 2| 用于发布日志事件的线程数。 |
| 最大发送重试次数| 30| 如果 broker 不可用或出现其他错误,重试发送消息的次数。重试的延迟时间如下:N ^ log(N)
,其中 N
是重试次数。 |
| 地址| | 以下形式的以逗号分隔的 broker 地址列表:host:port[,host:port]*
—— 覆盖 host
和 port
。 |
| 主机| localhost
是计算机网络中的一个术语,用于指代本地计算机。它通常被解析为 IP 地址 127.0.0.1
,这是 IPv4 中的环回地址。在 IPv6 中,对应的环回地址是 ::1
。localhost
常用于开发和测试环境,允许开发者在本地机器上运行服务器或应用程序,而无需连接到外部网络。| 连接的 RabbitMQ 主机。 |
| 端口| 5672| 连接到 RabbitMQ 的端口。 |
| virtualHost
是 Apache HTTP 服务器中的一个配置项,用于在同一台服务器上托管多个网站或应用程序。每个 virtualHost
可以配置不同的域名、IP 地址或端口,从而使得服务器能够根据请求的不同,将流量路由到不同的网站或应用程序。| /| 要连接的 RabbitMQ 虚拟主机。 |
| 用户名| 客人| 连接时使用的 RabbitMQ 用户。 |
| 密码| guest(客人)| 该用户的 RabbitMQ 密码。 |
| 使用 SSL| false| 是否对 RabbitMQ 连接使用 SSL。请参阅 RabbitConnectionFactoryBean 和配置 SSL。 |
| verifyHostname| true| 为 TLS 连接启用服务器主机名验证。请参阅 RabbitConnectionFactoryBean 和配置 SSL。 |
| sslAlgorithm| null| 使用的 SSL 算法。 |
| sslPropertiesLocation
| null| SSL 属性文件的位置。 |
| keyStore| null| 密钥库的位置。 |
| keyStorePassphrase
| null| 密钥库的密码短语。 |
| keyStoreType| JKS(Java KeyStore)是一种用于存储加密密钥和证书的文件格式。它是 Java 平台中常用的一种密钥库类型,通常用于存储私钥、公钥和相关的证书链。JKS 文件是二进制格式的,可以通过 Java 提供的工具(如 keytool
)来创建和管理。JKS 文件通常用于 Java 应用程序中,以支持 SSL/TLS 加密通信、数字签名等功能。
JKS 文件的安全性依赖于密码保护,因此在创建和使用 JKS 文件时,必须设置一个强密码来保护其内容。| 密钥库类型。 |
| trustStore
| null| 信任库的位置。 |
| trustStorePassphrase
是用于保护 trustStore
文件的密码短语。trustStore
是一个包含受信任证书的文件,通常用于在 SSL/TLS 通信中验证远程服务器的身份。trustStorePassphrase
用于加密和解密 trustStore
文件,确保其内容的安全性。| null| 信任库(truststore)的密码短语。 |
| trustStoreType| JKS| 信任库类型。 |
| ```plaintext
saslConfig
`saslConfig` 是用于配置 SASL(Simple Authentication and Security Layer,简单认证与安全层)的参数设置。SASL 是一种用于网络协议中提供认证和数据安全服务的框架。`saslConfig` 通常包含以下配置项:
- **mechanism**: 指定使用的认证机制,例如 `PLAIN`、`SCRAM-SHA-256` 等。
- **username**: 认证时使用的用户名。
- **password**: 认证时使用的密码。
- **protocol**: 指定使用的协议,例如 `SASL_SSL` 或 `SASL_PLAINTEXT`。
- **jaasConfig**: 用于配置 Java 的 JAAS(Java Authentication and Authorization Service,Java 认证与授权服务)文件路径或内容。
这些配置项通常用于 Kafka、Zookeeper 等分布式系统中,以确保通信的安全性和认证的可靠性。| null(RabbitMQ 客户端默认应用)| `saslConfig` - 请参阅 `RabbitUtils.stringToSaslConfig` 的 javadoc 以获取有效值。 |
| `contentType`| text/plain| `content-type` property of log messages. |
| `contentEncoding`| | `content-encoding` property of log messages. |
| 声明交换机| false| 是否在此 appender 启动时声明配置的 exchange。另请参阅 `durable` 和 `autoDelete`。 |
| 持久的| true| 当 `declareExchange` 为 `true` 时,`durable` 标志将被设置为该值。 |
| autoDelete| false| 当 `declareExchange` 为 `true` 时,auto-delete 标志将设置为该值。 |
| 字符集| null| 在将 `String` 转换为 `byte[]` 时使用的字符集。默认值:null(使用系统默认的字符集)。如果当前平台不支持该字符集,则回退到使用系统字符集。 |
| `deliveryMode` 是消息传递系统中的一个概念,用于指定消息的传递方式或模式。它通常用于定义消息的可靠性、持久性以及传递的保证级别。常见的 `deliveryMode` 包括:
- **1**:非持久性消息(Non-persistent)。消息在传递过程中不会持久化存储,如果消息代理(broker)崩溃,消息可能会丢失。
- **2**:持久性消息(Persistent)。消息会被持久化存储,即使消息代理崩溃,消息也不会丢失。
例如,在 AMQP(Advanced Message Queuing Protocol)或 JMS(Java Message Service)中,`deliveryMode` 是一个重要的属性,用于确保消息的可靠性传递。
```java
// 示例:设置消息的传递模式为持久性
message.setDeliveryMode(DeliveryMode.PERSISTENT);
在实际应用中,选择合适的 deliveryMode
取决于业务需求和对消息可靠性的要求。| 持久化| PERSISTENT
or NON_PERSISTENT
to determine whether or not RabbitMQ should persist the messages. |
| generateId| false| 用于确定 messageId
属性是否设置为唯一值。 |
| 客户端连接属性| null| 以逗号分隔的 key:value
键值对列表,用于自定义 RabbitMQ 连接的客户端属性。 |
| 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
时进行异步发布,事件很可能会由于交叉干扰而损坏。
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 appenders 会填充以下消息属性:
-
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);
}
}
}
自定义客户端属性
你可以通过添加字符串属性或更复杂的属性来添加自定义客户端属性。
简单字符串属性
每个 appender 支持向 RabbitMQ 连接添加客户端属性。
以下示例展示了如何为 logback 添加自定义客户端属性:
<appender name="AMQP" ...>
...
<clientConnectionProperties>thing1:thing2,cat:hat</clientConnectionProperties>
...
</appender>
<Appenders>
...
<RabbitMQ name="rabbitmq"
...
clientConnectionProperties="thing1:thing2,cat:hat"
...
</RabbitMQ>
</Appenders>
属性是一个以逗号分隔的 key:value
键值对列表。键和值不能包含逗号或冒号。
这些属性在查看连接时显示在 RabbitMQ 管理界面(Admin UI)上。
Logback 高级技巧
你可以继承 Logback 的 appender。这样做可以让你在客户端连接建立之前修改连接属性。以下示例展示了如何实现这一点:
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;
}
}
然后你可以在 logback.xml
中添加 <thing1>thing2</thing1>
。
对于前面示例中所示的 String 属性,可以使用上述技术。子类允许添加更丰富的属性(例如添加 Map
或数值属性)。
提供自定义队列实现
AmqpAppenders
使用 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>