需求分析
事情是这样的,我司有一套生产管理系统,是CS架构的,前端是C#
,后端是java
。由于前端经常出bug,每次都要去客户那边确认解决太过麻烦,老板让我把前端日志收集起来,方便运维和开发人员查看。需求很简单,但老板有一个要求,就是不能动前端代码,一行代码也不能加,一行代码也不能改,配置能改。
那就只能从前端原有的日志框架着手了,前端用的日志框架是log4net
,一番调查之后,发现可以使用日志框架的UdpAppender
实现,具体可查看官方文档。
前端配置
前端只需要在日志配置文件中加入一个appender
即可
<appender name="UdpAppender" type="log4net.Appender.UdpAppender">
<Encoding value="utf-8" />
<localPort value="38080" />
<remoteAddress value="172.23.223.110" />
<remotePort value="62345" />
<layout type="log4net.Layout.PatternLayout,log4net">
<param name="ConversionPattern" value="%d %-5p [%t] %c{1.} > %L %M - %X{userid} : %m%n" />
</layout>
</appender>
这边的userid
在用户登录之后放入ThreadContext
中,
ThreadContext.Stacks["userid"].Push(userid);
还是加了一行代码哈,不加也可以,但是就没办法知道前端是谁在操作,当然结合后端日志可以也查出是谁登录了。
后端UDP服务
Java中增加一个UDPService来接收客户端日志,基本代码如下:
@Slf4j
@Service
public class UdpService {
private static final int PORT = 62345;
private EventLoopGroup bossLoopGroup;
@PostConstruct
public void PostConstruct() throws Exception {
log.info("开始启动UdpService");
//表示服务器连接监听线程组,专门接受 accept 新的客户端client 连接
bossLoopGroup = new NioEventLoopGroup();
try {
//1,创建netty bootstrap 启动类
Bootstrap serverBootstrap = new Bootstrap();
//2、设置boostrap 的eventLoopGroup线程组
serverBootstrap = serverBootstrap.group(bossLoopGroup);
//3、设置NIO UDP连接通道
serverBootstrap = serverBootstrap.channel(NioDatagramChannel.class);
//4、设置通道参数 SO_BROADCAST广播形式
serverBootstrap = serverBootstrap.option(ChannelOption.SO_BROADCAST, true);
//5、设置处理类 装配流水线
serverBootstrap = serverBootstrap.handler(new MyUdpHandler(sysDataSource));
//6、绑定server,通过调用sync()方法异步阻塞,直到绑定成功
ChannelFuture f = serverBootstrap.bind(PORT).sync();
log.info("UdpService已开启,监听地址:{}", f.channel().localAddress());
//7、监听通道关闭事件,应用程序会一直等待,直到channel关闭
// f.channel().closeFuture().sync();
} catch (Exception e) {
log.error("UdpService启动失败", e);
System.exit(0);
// } finally {
// log.info("UdpServiceg关闭");
// //8 关闭EventLoopGroup,
// bossLoopGroup.shutdownGracefully();
}
}
@PreDestroy
public void PreDestroy() throws Exception {
if (bossLoopGroup != null) {
try {
Future f = bossLoopGroup.shutdownGracefully();
} catch (Exception e) {
log.error("UdpService关闭失败", e);
}
}
}
}
@Slf4j
class MyUdpHandler extends SimpleChannelInboundHandler<DatagramPacket> {
private Pattern pattern = Pattern.compile("^(\\d{4}-\\d{1,2}-\\d{1,2}\\s\\d{1,2}:\\d{1,2}:\\d{1,2}\\,\\d{1,3})\\s(INFO|DEBUG|WARN|ERROR)\\s{1,2}\\[(.*)\\]\\s(.*)\\s>\\s(\\d*)\\s(.*)\\s-\\s(.*)\\s:\\s(.*)$", Pattern.DOTALL);
@Override
protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception {
try{
String content = msg.content().toString(CharsetUtil.UTF_8);
log.info("从客户端【{}】IP:{},接收数据:\n{}", msg.sender().getAddress().getHostName(), msg.sender().getAddress().getHostAddress(), content);
// 使用正则分割日志信息,然后记录到任何你想保存的地方
Matcher matcher = pattern.matcher(content);
if (matcher.find()) {
String logtime = matcher.group(1);
String loglevel = matcher.group(2);
String threadtag = matcher.group(3);
String classname = matcher.group(4);
String linenumber = matcher.group(5);
String methodname = matcher.group(6);
String userid = matcher.group(7);
String message = matcher.group(8);
log.info(logtime + "|" + loglevel + "|" + threadtag + "|" + classname + "|" + linenumber + "|" + methodname + "|" + userid + "|" + message);
}
}
catch (Exception e){
}
}
}