文章目录
简介
Syslog4j是一个实现Syslog(RFC3164)协议的Java开源类库包括客户端与服务器端。通过 UDP/IP, TCP/IP, TCP/IPover SSL/TLS, Unix Syslog, 和 Unix Socket 等协议进行日志记录。
本文使用UDP协议发送日志给总线(这里说的总线只是我们公司有个平台专门管理这些日志)
Maven依赖
<!-- 具体版本可以自己去Maven中央仓库去寻找 -->
<dependency>
<groupId>org.graylog2</groupId>
<artifactId>syslog4j</artifactId>
<version>0.9.60</version>
</dependency>
UDP 模式
搭建日志接收服务端
import org.graylog2.syslog4j.SyslogConstants;
import org.graylog2.syslog4j.server.*;
import java.net.SocketAddress;
public class MySyslogServer {
private static final String HOST = "127.0.0.1";
private static final int PORT = 32376;
private void receiveSyslogMessage() throws InterruptedException {
SyslogServerIF server = SyslogServer.getInstance(SyslogConstants.UDP);
SyslogServerConfigIF config = server.getConfig();
config.setHost(HOST);
config.setPort(PORT);
config.addEventHandler(new SyslogServerSessionEventHandlerIF() {
@Override
public Object sessionOpened(SyslogServerIF syslogServerIF, SocketAddress socketAddress) {
return null;
}
@Override
public void event(Object o, SyslogServerIF syslogServerIF, SocketAddress socketAddress, SyslogServerEventIF syslogServerEventIF) {
System.out.println("receive from:" + socketAddress + "message" + syslogServerEventIF.getMessage());
}
@Override
public void exception(Object o, SyslogServerIF syslogServerIF, SocketAddress socketAddress, Exception e) {
}
@Override
public void sessionClosed(Object o, SyslogServerIF syslogServerIF, SocketAddress socketAddress, boolean b) {
}
@Override
public void initialize(SyslogServerIF syslogServerIF) {
}
@Override
public void destroy(SyslogServerIF syslogServerIF) {
}
});
SyslogServer.getThreadedInstance(SyslogConstants.UDP);
Thread.sleep(100000);
}
public static void main(String[] args) throws InterruptedException {
System.out.println("Syslog Server is start.");
new MySyslogServer().receiveSyslogMessage();
}
}
搭建日志发送客户端
package cn.com.mcd.data.syslog;
import com.alibaba.fastjson.JSONObject;
import org.graylog2.syslog4j.Syslog;
import org.graylog2.syslog4j.SyslogConstants;
import org.graylog2.syslog4j.SyslogIF;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
public class MySyslogClient {
private static final String HOST = "127.0.0.1";
private static final int PORT = 32376;
public void generate() {
SyslogIF syslog = Syslog.getInstance(SyslogConstants.UDP);
syslog.getConfig().setHost(HOST);
syslog.getConfig().setPort(PORT);
try {
// 这里就是发送日志,当前它的API接口有很多种,都可以尝试一样
// 参数: 10 ----> 表示日志等级 当一个单体应用中,存在多个任务发送日志,这时候级别的作用就体现出现了
// URLDecoder.decode("测试发送"+ System.currentTimeMillis(), "UTF-8") 表示发送内容以及编码格式
syslog.log(10, URLDecoder.decode("测试发送"+ System.currentTimeMillis(), "UTF-8"));
} catch (UnsupportedEncodingException e) {
System.out.println("generate log get exception " + e);
}
}
public static void main(String[] args) {
new MySyslogClient().generate();
}
}
TCP 模式
搭建日志接收服务端
import org.graylog2.syslog4j.SyslogConstants;
import org.graylog2.syslog4j.server.*;
import java.net.SocketAddress;
public class MySyslogServer {
private static final String HOST = "127.0.0.1";
private static final int PORT = 32376;
private void receiveSyslogMessage() throws InterruptedException {
SyslogServerIF server = SyslogServer.getInstance(SyslogConstants.TCP);
SyslogServerConfigIF config = server.getConfig();
config.setHost(HOST);
config.setPort(PORT);
config.addEventHandler(new SyslogServerSessionEventHandlerIF() {
@Override
public Object sessionOpened(SyslogServerIF syslogServerIF, SocketAddress socketAddress) {
return null;
}
@Override
public void event(Object o, SyslogServerIF syslogServerIF, SocketAddress socketAddress, SyslogServerEventIF syslogServerEventIF) {
System.out.println("receive from:" + socketAddress + "message" + syslogServerEventIF.getMessage());
}
@Override
public void exception(Object o, SyslogServerIF syslogServerIF, SocketAddress socketAddress, Exception e) {
}
@Override
public void sessionClosed(Object o, SyslogServerIF syslogServerIF, SocketAddress socketAddress, boolean b) {
}
@Override
public void initialize(SyslogServerIF syslogServerIF) {
}
@Override
public void destroy(SyslogServerIF syslogServerIF) {
}
});
SyslogServer.getThreadedInstance(SyslogConstants.TCP);
Thread.sleep(100000);
}
public static void main(String[] args) throws InterruptedException {
System.out.println("Syslog Server is start.");
new MySyslogServer().receiveSyslogMessage();
}
}
搭建日志发送客户端
import com.alibaba.fastjson.JSONObject;
import org.graylog2.syslog4j.Syslog;
import org.graylog2.syslog4j.SyslogConstants;
import org.graylog2.syslog4j.SyslogIF;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
public class MySyslogClient {
private static final String HOST = "127.0.0.1";
private static final int PORT = 32376;
public void generate() {
SyslogIF syslog = Syslog.getInstance(SyslogConstants.TCP);
syslog.getConfig().setHost(HOST);
syslog.getConfig().setPort(PORT);
try {
// 这里就是发送日志,当前它的API接口有很多种,都可以尝试一样
// 参数: 10 ----> 表示日志等级 当一个单体应用中,存在多个任务发送日志,这时候级别的作用就体现出现了
// URLDecoder.decode("测试发送"+ System.currentTimeMillis(), "UTF-8") 表示发送内容以及编码格式
syslog.log(10, URLDecoder.decode("测试发送"+ System.currentTimeMillis(), "UTF-8"));
} catch (UnsupportedEncodingException e) {
System.out.println("generate log get exception " + e);
}
// 切记这里要加上,因为TCP协议的长链接(也就是握手特点)
Thread.sleep(10000)
}
public static void main(String[] args) {
new MySyslogClient().generate();
}
}
使用注意事项
UDP 发送设置报文大小问题
UDP发送信息,默认数据体不能超过1k,不然就会被默认拆包,因为本公司业务场景中会把特定时间的哪些用户执行哪些SQL(SQL估摸着至少4000个字符串)发送给总线,这时候就会出现以下情况:
4kb的数据被拆分为了5个包,会导致接收端出现拼接符号…
这里给出的解决方案设置报文大小
// 实例化Syslog的时候
SyslogIF syslog = Syslog.getInstance(SyslogConstants.UDP);
syslog.getConfig().setHost(HOST);
syslog.getConfig().setPort(PORT);
syslog.getConfig().setMaxMessageLength(65507); // 64KB
因为安全规则以及职业道德,这里就不展示修改为50MB发送日志的成功场景,大家可以依据自己的业务范围设置大小发送。
乱码问题
第一玩着这个功能的时候,没有设置编码格式,导致中文字符(GBK)到服务器端接收日志的时候,出现乱码
URLDecoder.decode(String var)
使用以上方法可以规避这些问题
后续问题会持续录入
MaxMessageLength 有限制吗?
有,64KB就是最大限度,这个限制不是Syslog4j做的限制,是底层DatagramPacket.java的限制。超过64KB就会拆包,也就截断报文分多次发送。如果你直接给个50MB的限制,底层就会抛出异常,最恶心的就是Syslog4j捕获了异常,但是没有跑出来,并且出现异常后会重新实例化发送。
正常来说,假设你的内容是70kb,但是你想一次性全部发出去,你把MaxMessageLength设置到大于64KB,底层会出现这个异常
TCP发送拆分发送
这里的报文分行分送,后来发现我发送的文本就是分行的,因此,我们只要对读出字符流String,通过正则替换成整行,就可以直接一个包发过去了,前提也是需要配置MaxMessageLength的属性值大小
// 无论你是读文件,还是JSON序列化对象,只要是字符串就可以了,但是保不齐的来源存在换行符 因此需要借助下面的正则来替换成单行
String modelStr = "";
String reg = "\\s+";
modelStr = modelStr.replaceAll(reg, " ");
总结
建议大家在使用这个工具的时候,下载一个WireShark这一类抓包软件,可以进行本地抓包,用来判断是否发送出去了,以及发送的时候出现的问题,例如UDP报文大小,我一开始也是不知道,后来总线那边的同事提醒我了,我也观察到了那些省略号分布很规律,就意识到大事不妙了,但是控制台打印的日志是完整的,也就说在传输的过程中肯定是出了什么问题,无论是TCP还是UDP都已经在计算机的传输层了,我们是看不到它们的流程的,这时候抓包工具就至关重要了。