syslog 服务器配置

配置 rsyslog 配置文件/etc/rsyslog.conf

rsyslog 服务端接收

作为日志聚合服务,接受客户端发送过来的日志

开启 UDP/TCP 接收:

# Provides UDP syslog reception
$ModLoad imudp
$UDPServerRun 514

# Provides TCP syslog reception
$ModLoad imtcp
$InputTCPServerRun 514

配置日志接收规则:

$template RemoteLogs,"/data/logs/%fromhost-ip%/%$YEAR%-%$MONTH%-%$DAY%.log"
:fromhost-ip, !isequal, "127.0.0.1" ?RemoteLogs
#& ~

rsyslog 客户端发送

rsyslog 也可以作为客户端向远程的 rsyslog 服务端转发日志

开启转发:

# UDP转发
*.* @server_ip:514

# TCP转发
*.* @@server_ip:514

重启 rsyslog 服务

当 rsyslog 服务端或客户端配置完成后,重启服务使配置生效

systemctl restart rsyslog
systemctl status rsyslog

配置支持 TLS 的 rsyslog

安装依赖包

配置支持 TLS 的 rsyslog 需要安装以下依赖包

yum install rsyslog-gnutls
yum install rsyslog
yum install gnutls-utils
yum install gnutls

生成密钥与证书

# 生成根证书
certtool --generate-privkey --outfile ca-key.pem
certtool --generate-self-signed --load-privkey ca-key.pem --outfile ca.pem

# 生成服务端密钥与证书
certtool --generate-privkey --outfile rslserver-key.pem --bits 2048
certtool --generate-request --load-privkey rslserver-key.pem --outfile request.pem
certtool --generate-certificate --load-request request.pem --outfile rslserver-cert.pem --load-ca-certificate ca.pem --load-ca-privkey ca-key.pem

# 生成客户端密钥与证书
certtool --generate-privkey --outfile rslclient-key.pem --bits 2048
certtool --generate-request --load-privkey rslclient-key.pem --outfile request.pem
certtool --generate-certificate --load-request request.pem --outfile rslclient-cert.pem --load-ca-certificate ca.pem --load-ca-privkey ca-key.pem

注意:输入证书信息时名字不要留空,不然后面 Java 中会报java.security.cert.CertificateParsingException: X.509 Certificate is incomplete: subject field is empty的错误

配置服务端接收 TLS 连接

$DefaultNetstreamDriver gtls
$DefaultNetstreamDriverCAFile /root/tls/ca.pem
$DefaultNetstreamDriverCertFile /root/tls/rslserver-cert.pem
$DefaultNetstreamDriverKeyFile /root/tls/rslserver-key.pem

# Provides TCP syslog reception
$ModLoad imtcp
$InputTCPServerRun 514

$InputTCPServerStreamDriverAuthMode anon
$InputTCPServerStreamDriverMode 1
$ActionSendStreamDriverAuthMode x509/name
$ActionSendStreamDriverMode 1
$MaxOpenFiles 2048

配置客户端启用 TLS 连接

$DefaultNetstreamDriver gtls

$DefaultNetstreamDriverCAFile /root/tls/private/ca.pem
$DefaultNetstreamDriverCertFile /root/tls/private/rslclient-cert.pem
$DefaultNetstreamDriverKeyFile /root/tls/private/rslclient-key.pem

$ActionSendStreamDriverPermittedPeer your.server.com # that should be your rsyslog server
$ActionSendStreamDriverMode 1 # run driver in TLS-only mode
$ActionSendStreamDriverAuthMode x509/name

注意:如果证书报错的话,可以改为$ActionSendStreamDriverAuthMode anon,即不需认证证书。

Log4j 转发日志

Log4j 1.X 转发 syslog

这里 Log4j 的版本是 1.2.17,配置 log4j 配置文件开启 syslog 转发

log4j.rootLogger=INFO, console, fout, SYSLOG

log4j.appender.SYSLOG=org.apache.log4j.net.SyslogAppender
log4j.appender.SYSLOG.threshold=INFO
log4j.appender.SYSLOG.syslogHost={SYSLOG_HOST}
log4j.appender.SYSLOG.facility=LOCAL4
log4j.appender.SYSLOG.layout=org.apache.log4j.PatternLayout
log4j.appender.SYSLOG.layout.conversionPattern=%d{HH:mm:ss,SSS} %-5p %-60c %x - %m%n

需要在代码中导入系统变量

System.setProperty("SYSLOG_HOST", "127.0.0.1");

Log4j2 中转发 syslog

maven 依赖

<!--Log4j 2 dependencies-->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>${log4j2-version}</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>${log4j2-version}</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>${log4j2-version}</version>
</dependency>

配置模版

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" monitorInterval="5">
    <Properties>
        <Property name="console-layout">%d{HH:mm:ss} %-5level %logger{36} - %msg%n</Property>
    </Properties>
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="${console-layout}"/>
        </Console>
        <Syslog name="udp-syslog" host="localhost" port="514" protocol="UDP">
            <PatternLayout pattern="${console-layout}"/>
        </Syslog>
        <Syslog name="tcp-syslog" host="localhost" port="514" protocol="TCP">
            <PatternLayout pattern="${console-layout}"/>
        </Syslog>
    </Appenders>
    <Loggers>
        <Root level="error">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="tcp-syslog"/>
        </Root>
    </Loggers>
</Configuration>

使用 TLS 转发日志

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" monitorInterval="5">
    <Properties>
        <Property name="console-layout">%d{HH:mm:ss} %-5level %logger{36} - %msg%n</Property>
    </Properties>
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="${console-layout}"/>
        </Console>
        <Syslog name="tls-syslog" host="localhost" port="6514">
            <SSL>
                <TrustStore location="truststore.jks"      password="KEYSTORE_PASSWORD"/>
            </SSL>
        </Syslog>
        <Syslog name="mtls-syslog" host="localhost" port="6514">
            <SSL>
                <KeyStore   location="log4j2-keystore.jks" password="KEYSTORE_PASSWORD"/>
                <TrustStore location="truststore.jks"      password="KEYSTORE_PASSWORD"/>
            </SSL>
        </Syslog>
    </Appenders>
    <Loggers>
        <Root level="error">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="tls-syslog"/>
        </Root>
    </Loggers>
</Configuration>

其中,如果是单向 TLS,只需要提供 Rsyslog 服务端的公钥证书,存入 TrustStore 中即可,配置中只需要指定 TrustStore。如果需要双向 TLS,则需要生成客户端的私钥与证书,存入 KeyStore 中。

读取证书与私钥

通过以下代码将 base64 编码的证书或私钥读入程序中

private static X509Certificate base64toCert(String base64String) throws CertificateException {
    byte[] decoded = Base64.getDecoder().decode(base64String);
    CertificateFactory cf = CertificateFactory.getInstance("X.509");
    return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(decoded));
}

private static RSAPrivateKey base64toPrivateKey(String base64String) throws NoSuchAlgorithmException, InvalidKeySpecException {
    byte[] decoded = Base64.getDecoder().decode(base64String);
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decoded);
    return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
}

注意:私钥如果不是 pkcs8 格式的,上面代码无法读取,可以通过以下命令转换私钥格式

certtool --to-p8 --load-privkey rslclient-key.pem --outfile rslclient-key-8.pem

将证书和私钥存入 KeyStore

KeyStore keyStore = KeyStore.getInstance("jks");
String password = "yourpassword";
X509Certificate caCert = base64toCert(caBase64String);

// create new key store
keyStore.load(null, password.toCharArray());

// store certificate
keyStore.setCertificateEntry("cert name", caCert);

// store private key
X509Certificate[] certChain = new X509Certificate[2];
certChain[0] = cert;
certChain[1] = caCert;
Key privateKey = base64toPrivateKey(keyBase64String);
keyStore.setKeyEntry("key name", privateKey, password.toCharArray() , certChain);

// save to file
FileOutputStream fos = new FileOutputStream(trustStoreFileName);
keyStore.store(fos, password.toCharArray());

动态修改 Log4j2 配置

在配置文件中加上monitorInterval属性

<Configuration monitorInterval="5" status="WARN">
</Configuration>

在程序中指定配置文件的位置,这样就会定期拉取最新的配置文件。注意,需要在所有的 logger 启动之前执行。

// set log4j2 configuration file location
LoggerContext context = (LoggerContext) LogManager.getContext(false);
File log4j2XmlFile = new File("log4j2.xml");
context.setConfigLocation(log4j2XmlFile.toURI());

接下来只需要修改"log4j2.xml"文件即可

修改 XML 文件

用 Apache Commons Configuration 来修改 log4j2.xml 配置文件

// 读取默认配置
File log4j2XmlFile = new File("log4j2.xml");
Parameters params = new Parameters();
FileBasedConfigurationBuilder<FileBasedConfiguration> builder = new FileBasedConfigurationBuilder<FileBasedConfiguration>(XMLConfiguration.class)
        .configure(params.fileBased()
                .setFile(log4j2XmlFile));
Configuration config = builder.getConfiguration();

// 修改配置
String configNode = "Appenders.Syslog(1)"; // Appreders下有多个Syslog,所以需要指定第几个Syslog
config.addProperty(configNode + ".PatternLayout.[@pattern]", "${remote-layout}") // 为节点添加属性

// 保存配置
builder.save();

maven 依赖

<!--Apache Commons Configuration dependency-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-configuration2</artifactId>
    <version>${apache.commons.configuration-version}</version>
</dependency>
<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.9.4</version>
</dependency>

拷贝默认文件

可以用 Apache Commons IO 中的FileUtils.copyFile来拷贝文件

File originLog4j2XmlFile = new File("log4j2-origin.xml");
File log4j2XmlFile = new File("log4j2.xml");
FileUtils.copyFile(originLog4j2XmlFile, log4j2XmlFile);

maven 依赖

<!--Apache Commons IO dependency-->
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.8.0</version>
</dependency>

References