帮助中心 >  技术知识库 >  云服务器 >  服务器教程 >  Java用webSocket实现tomcat的日志实时输出到web页面

Java用webSocket实现tomcat的日志实时输出到web页面

2018-12-20 08:55:28 3224

  1. 1.场景需求

  2. 后台攻城狮和前端攻城狮一起开发时,经常受到前端攻城狮的骚扰,动不动就来一句,那谁,帮我看一下接口访问出什么错了。。。我刚刚上传的参数过来了吗。。。你返回的是什么。。。我请求过去了吗。。。

  3. 好吧,就是这样的一种情况,然后我希望让他们自己去看后台日志,而又不想给他们登陆服务器的权限TAT。那就想办法把访问日志实时输出到web页面,这样他们打开页面就可以了。

  4. 2.特别鸣谢

  5. 1)特别感谢http://blog.csdn.net/xiao__gui/article/details/50041673的启发,该文章中利用的是linux中的tail日志,本文即是受此启发,基本思路一模一样,感谢感谢。

  6. 2)感谢国产博客中各种websocket教程,获益颇多,本文其实也是一个websocket教程,只是稍作拓展。

  7. 3.进入正题

  8. 配置websocket,大部分web项目都会用到spring框架吧,所有这里贴spring websocket的配置,不用spring的请自行去掉spring部分。spring使用4.0以上,tomcat使用7.0.68版本。

  9. 1)pom.xml中包的引入:


<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.3.3</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
<version>4.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>4.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
  1. 2)websocket逻辑代码,需要一个config和一个handle

  2. configuration:


  1. package com.he.websocket;

  2. import org.springframework.context.annotation.Bean;

  3. import org.springframework.context.annotation.Configuration;

  4. import org.springframework.web.socket.config.annotation.EnableWebSocket;

  5. import org.springframework.web.socket.config.annotation.WebSocketConfigurer;

  6. import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

  7. import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;

  8. /**

  9. * WebScoket配置处理器

  10. * @author he

  11. * @Date 2016年03月15日 下午1:15:09

  12. */

  13. @Configuration

  14. @EnableWebSocket

  15. public class WebsocketConfig implements WebSocketConfigurer {

  16. public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {

  17. registry.addHandler(myHandler(), "/ws").addInterceptors(new HttpSessionHandshakeInterceptor());

  18. registry.addHandler(myHandler(), "/ws/sockjs").addInterceptors(new HttpSessionHandshakeInterceptor()).withSockJS();

  19. }

  20. @Bean

  21. public WebsocketHandler myHandler() {

  22. return new WebsocketHandler();

  23. }

  24. }

  25. handle:

  26. package com.he.websocket;

  27. import java.io.IOException;

  28. import java.util.Date;

  29. import java.util.HashMap;

  30. import java.util.Iterator;

  31. import java.util.Map;

  32. import java.util.Map.Entry;

  33. import org.apache.log4j.Logger;

  34. import org.springframework.stereotype.Component;

  35. import org.springframework.web.socket.CloseStatus;

  36. import org.springframework.web.socket.TextMessage;

  37. import org.springframework.web.socket.WebSocketMessage;

  38. import org.springframework.web.socket.WebSocketSession;

  39. import org.springframework.web.socket.handler.TextWebSocketHandler;

  40. import com.google.gson.Gson;

  41. import com.google.gson.GsonBuilder;

  42. import com.he.entity.Message;

  43. @Component

  44. public class WebsocketHandler extends TextWebSocketHandler {

  45. protected static final Logger LOG = Logger.getLogger(WebsocketHandler.class);

  46. public static final Map<Object, WebSocketSession> userSocketSessionMap;

  47. static {

  48. userSocketSessionMap = new HashMap<Object, WebSocketSession>();

  49. }

  50. /**

  51.     * 建立连接后

  52.     */

  53. public void afterConnectionEstablished(WebSocketSession session)

  54. throws Exception {

  55. String uid = (String) session.getAttributes().get("uid");

  56. if (userSocketSessionMap.get(uid) == null) {

  57. userSocketSessionMap.put(uid, session);

  58. }

  59. LOG.warn("======建立连接完成======");

  60. }

  61. /**

  62.     * 消息处理,在客户端通过Websocket API发送的消息会经过这里,然后进行相应的处理

  63.     */

  64. @Override

  65. public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {

  66. if(message.getPayloadLength()==0)return;

  67. Message msg=new Gson().fromJson(message.getPayload().toString(),Message.class);

  68. String msgString = message.getPayload().toString();

  69. LOG.warn("收到的消息是:" + msgString);

  70. LOG.warn("发送的对象是:" + msg.getTo());

  71. msg.setDate(new Date());

  72. sendMessageToUser(msg.getTo(), new TextMessage(new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create().toJson(msg)));

  73. LOG.warn("======消息处理结束======");

  74. }

  75. /**

  76.     * 消息传输错误处理

  77.     */

  78. public void handleTransportError(WebSocketSession session,

  79.            Throwable exception) throws Exception {

  80. if (session.isOpen()) {

  81. session.close();

  82. }

  83. Iterator<Entry<Object, WebSocketSession>> it = userSocketSessionMap

  84. .entrySet().iterator();

  85. LOG.warn("======消息传输错误======");

  86. // 移除Socket会话

  87. while (it.hasNext()) {

  88. Entry<Object, WebSocketSession> entry = it.next();

  89. if (entry.getValue().getId().equals(session.getId())) {

  90. userSocketSessionMap.remove(entry.getKey());

  91. System.out.println("Socket会话已经移除:用户ID" + entry.getKey());

  92. break;

  93. }

  94. }

  95. }

  96. /**

  97.     * 关闭连接后

  98.     */

  99. public void afterConnectionClosed(WebSocketSession session,

  100.            CloseStatus closeStatus) throws Exception {

  101. LOG.warn("Websocket:" + session.getId() + "已经关闭");

  102. Iterator<Entry<Object, WebSocketSession>> it = userSocketSessionMap

  103. .entrySet().iterator();

  104. // 移除Socket会话

  105. LOG.warn("======关闭连接======");

  106. while (it.hasNext()) {

  107. Entry<Object, WebSocketSession> entry = it.next();

  108. if (entry.getValue().getId().equals(session.getId())) {

  109. userSocketSessionMap.remove(entry.getKey());

  110. LOG.warn("Socket会话已经移除:用户ID" + entry.getKey());

  111. break;

  112. }

  113. }

  114. }

  115. public boolean supportsPartialMessages() {

  116. return false;

  117. }

  118. /**

  119.     * 给所有在线用户发送消息

  120.     *

  121.     * @param message

  122.     * @throws IOException

  123.     */

  124. public void broadcast(final TextMessage message) throws IOException {

  125. Iterator<Entry<Object, WebSocketSession>> it = userSocketSessionMap

  126. .entrySet().iterator();

  127. LOG.warn("======群发======");

  128. // 多线程群发

  129. while (it.hasNext()) {

  130. final Entry<Object, WebSocketSession> entry = it.next();

  131. if (entry.getValue().isOpen()) {

  132. // entry.getValue().sendMessage(message);

  133. new Thread(new Runnable() {

  134. public void run() {

  135. try {

  136. if (entry.getValue().isOpen()) {

  137. entry.getValue().sendMessage(message);

  138. }

  139. } catch (IOException e) {

  140. e.printStackTrace();

  141. }

  142. }

  143. }).start();

  144. }

  145. }

  146. }

  147. /**

  148.     * 给某个用户发送消息

  149.     *

  150.     * @param userName

  151.     * @param message

  152.     * @throws IOException

  153.     */

  154. public void sendMessageToUser(String uid, TextMessage message)

  155. throws IOException {

  156. WebSocketSession session = userSocketSessionMap.get(uid);

  157. LOG.warn("======给某个用户发送消息======");

  158. if (session != null && session.isOpen()) {

  159. session.sendMessage(message);

  160. }

  161. }

  162. }

  163. 3)web.xml中配置spring和springMVC,因为借助与springMVC来拦截处理websocket请求,如果用其他MVC框架,请自行替换。不想换也可以用springMVC,侵入性很低,并且本来就可以多种MVC共存。

  164. <context-param>

  165. <param-name>contextConfigLocation</param-name>

  166. <param-value>

  167. /WEB-INF/classes/applicationContext.xml

  168. </param-value>

  169. </context-param>

  170. <servlet>

  171. <servlet-name>SpringMVC</servlet-name>

  172. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

  173.        <init-param>

  174.            <param-name>contextConfigLocation</param-name>

  175.            <param-value>/WEB-INF/classes/applicationContext.xml</param-value>

  176.        </init-param>

  177.        <load-on-startup>1</load-on-startup>

  178.    </servlet>

  179.    <servlet-mapping>

  180.        <servlet-name>SpringMVC</servlet-name>

  181.        <url-pattern>*.do</url-pattern>

  182.    </servlet-mapping>

  183. 注意web.xml的头部:

  184. <?xml version="1.0" encoding="UTF-8"?>

  185. <web-app version="3.0" xmlns="http://www.landui.com/xml/ns/javaee"

  186. xmlns:xsi="http://www.landui.com/2001/XMLSchema-instance"

  187. xsi:schemaLocation="http://www.landui.com/xml/ns/javaee

  188.    http://www.landui.com/xml/ns/javaee/web-app_3_0.xsd">

  189. 4)spring的xml文件中对websocket的配置:

  190. <!-- websocket配置 -->

  191. <bean id="websocket" class="com.he.websocket.WebsocketHandler" />

  192. <websocket:handlers>

  193. <websocket:mapping path="/ws.do" handler="websocket" />

  194. <websocket:handshake-interceptors>

  195. <bean class="org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor" />

  196. </websocket:handshake-interceptors>

  197. </websocket:handlers>

  198. <!-- 需要扫描的包-->

  199. <context:component-scan base-package="com.he.websocket.WebsocketConfig" />

  200. <context:component-scan base-package="com.he.websocket.WebsocketHandler" />

  201. 注意spring的xml的头部:

  202. <?xml version="1.0" encoding="UTF-8"?>

  203. <beans xmlns="http://www.landui.com/schema/beans"

  204. xmlns:websocket="http://www.landui.com/schema/websocket"

  205. xmlns:context="http://www.landui.com/schema/context"

  206. xmlns:mvc="http://www.landui.com/schema/mvc"

  207. xmlns:xsi="http://www.landui.com/2001/XMLSchema-instance" xmlns:p="http://www.landui.com/schema/p"

  208. xsi:schemaLocation="http://www.landui.com/schema/beans

  209.                        http://www.landui.com/schema/beans/spring-beans-4.0.xsd

  210.                        http://www.landui.com/schema/context  

  211.                        http://www.landui.com/schema/context/spring-context-4.0.xsd  

  212.                        http://www.landui.com/schema/websocket

  213.                        http://www.landui.com/schema/websocket/spring-websocket.xsd

  214.                        http://www.landui.com/schema/mvc  

  215.                        http://www.landui.com/schema/mvc/spring-mvc-4.0.xsd

  216.                         ">

  217. 5)日志接收页面:

  218. <!DOCTYPE html>

  219. <html>

  220. <head>

  221. <meta charset="utf-8">

  222. <title>tail log</title>

  223. <script src="//cdn.bootcss.com/jquery/2.1.4/jquery.js"></script>

  224. <style>

  225. html,body

  226. {

  227. height:100%;

  228. width:100%;

  229. }

  230. </style>

  231. </head>

  232. <body>

  233. <div id="log-container" style="height: 100%; overflow-y: scroll; background: #333; color: #aaa; padding: 10px;">

  234. <div>

  235. </div>

  236. </div>

  237. </body>

  238. <script>

  239. $(document).ready(function() {

  240. // 指定websocket路径

  241. var websocket = new WebSocket('ws://localhost:8080/websocket/ws.do');

  242. websocket.onmessage = function(event) {

  243. // 接收服务端的实时日志并添加到HTML页面中

  244. $("#log-container div").append(event.data + "<p> </p>");

  245. // 滚动条滚动到最低部

  246. $("#log-container").scrollTop($("#log-container div").height() - $("#log-container").height());

  247. };

  248. });

  249. </script>

  250. </body>

  251. </html>

  252. 此页面打开时发送了一个websocket请求,ws.do,会被springMVC拦截处理。连接建立之后每当收到新的消息会追加到当前文本的末尾并换行(p标签用来换行)。

  253. 6)控制日志的实时打印示例:

  254. PrintWriter out = response.getWriter();

  255. MyWebSocketHandler.broadcast(new TextMessage(new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create().toJson("访问test接口")));

  256. out.println("访问test接口");

  257. 每次返回给前端某些值的时候通过一个广播将该值传送到web页面。如果不想广播,只需要在程序中对log的session进行特别的标签,然后定向传送即可。

  258. 效果示意图:浏览器输入localhost:8080/projectName/log.html,然后访问localhost:8080/projectName/test,每次访问test接口,网页上实时显示tomcat输出的日志。

  259. 4.备注:

  260. 需要完整代码留邮箱,看到就发送,发送的工程是一个基本的web工程,只集成spring websocket,本文的所有效果和引用都有。或者发邮件到smile326@qq.com提醒我。

  261. 本例中为方便消息的处理用到如下两个实体类(我也是沿用了别人教程代码):

  262. Message.java:

  263. public class Message {

  264. //发送者

  265. public Long from;

  266. //发送者名称

  267. public String fromName;

  268. //接收者

  269. public Long to;

  270. //发送的文本

  271. public String text;

  272. //发送日期

  273. public Date date;

  274. public Long getFrom() {

  275. return from;

  276. }

  277. public void setFrom(Long from) {

  278. this.from = from;

  279. }

  280. public Long getTo() {

  281. return to;

  282. }

  283. public void setTo(Long to) {

  284. this.to = to;

  285. }

  286. public String getText() {

  287. return text;

  288. }

  289. public void setText(String text) {

  290. this.text = text;

  291. }

  292. public String getFromName() {

  293. return fromName;

  294. }

  295. public void setFromName(String fromName) {

  296. this.fromName = fromName;

  297. }

  298. public Date getDate() {

  299. return date;

  300. }

  301. public void setDate(Date date) {

  302. this.date = date;

  303. }

  304. user.java:

  305. public class User {

  306. private Long id;

  307. private String name;

  308. private String password;

  309. public Long getId() {

  310. return id;

  311. }

  312. public void setId(Long id) {

  313. this.id = id;

  314. }

  315. public String getName() {

  316. return name;

  317. }

  318. public void setName(String name) {

  319. this.name = name;

  320. }

  321. public String getPassword() {

  322. return password;

  323. }

  324. public void setPassword(String password) {

  325. this.password = password;

  326. }

  327. }

  328. 应大家需求,完成工程的代码放在了github上面啦!传上去的代码进一步完善了一下,把建立连接的时候上传参数的问题处理了一下;把存放session的容器换成了线程安全型的!欢迎大家在线讨论!

  329. https://github.com/smile326/springWebsockeForTomcatLog


提交成功!非常感谢您的反馈,我们会继续努力做到更好!

这条文档是否有帮助解决问题?

非常抱歉未能帮助到您。为了给您提供更好的服务,我们很需要您进一步的反馈信息:

在文档使用中是否遇到以下问题: