前端技术之:如何在控制台将JS class实例输出为JSON格式

本文介绍在Node.js中如何自定义控制台输出类对象的格式,通过重写类的[util.inspect.custom]函数,实现更简洁的JSON格式输出,避免每次调用JSON.stringify。

有一个类:

class Point {

  constructor(x, y) {

    this.x = x;

    this.y = y;

  }

}

如果我们在控制台中输出其实例:

console.log(new Point(10, 20));

 

控制台中的输出结果为:

Point { x: 10, y: 20 }

 

那如何只输出JSON格式,不输出类名”Point”呢?

 

有的同学可能会使用如下的方法:

console.log(JSON.stringify(new Point(10, 20)))

 

这种方法当然是可以的,其输出结果如下:

{"x":10,"y":20}

 

但我们每次输出的时候,都需要调用一次JSON.stringify,显得有些啰嗦。

有没有一种更简洁的办法呢?

答案是肯定的。

实际上,如果你使用的是nodejs,console.log输出类对象时,是调用的inspect函数来序列化并打印输出对象的。

 

而在node中有一种自定义对象inspection函数的办法。

在6.6.0以上版本中,你可以重写类的[util.inspect.custom](depth, options)函数。


const util = require('util');


class Point {

  constructor(x, y) {

    this.x = x;

    this.y = y;

  }


  toString() {

    const that = this;

    return JSON.stringify(that);

  }


  [util.inspect.custom](depth, options) {

    return this.toString()

  }

}

8.x版本的文档说明:https://nodejs.org/docs/latest-v8.x/api/util.html

const inspect = Symbol.for('nodejs.util.inspect.custom');


class Point {

  constructor(x, y) {

    this.x = x;

    this.y = y;

  }


  toString() {

    const that = this;

    return JSON.stringify(that);

  }


  [inspect]() {

    return this.toString()

  }

}


console.log(new Point(10, 20));

在node v10.12.0以上版本中,使用了Symbol,并可以重写[inspect]()函数。

 

相关文档为:https://nodejs.org/api/util.html#util_util_inspect_custom

<think>我们需要将logback生成的日志通过WebSocket实时发送到前端展示。这通常涉及以下步骤: 1. 配置logback,使其将日志事件发送给一个自定义的appender,而不是(或同时)控制台或文件。 2. 自定义的appender将日志事件发给WebSocket处理程序。 3. WebSocket处理程序管理连接并将日志事件推送给所有连接的前端客户端。 我们将使用以下技术栈(假设): - Java作为后端语言 - Logback作为日志框架 - Spring Boot(可选,但这里使用以简化WebSocket配置) 步骤: 1. 添加依赖(Maven): - Spring Boot Starter Web - Spring Boot Starter WebSocket - Logback 2. 配置WebSocket: - 创建一个WebSocket配置类启用WebSocket。 - 创建一个WebSocket处理程序来处理连接和消息。 3. 创建自定义Logback Appender: - 这个appender将捕获日志事件,并将其发送到WebSocket处理程序,然后由WebSocket处理程序广播给所有连接的客户端。 4. 前端:使用WebSocket连接并接收日志消息。 注意:由于日志事件可能非常频繁,我们需要考虑性能问题。但为了简单示例,我们不考虑缓冲和批量处理。 详细代码实现: ### 1. 添加依赖(pom.xml) ```xml <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> </dependencies> ``` ### 2. 配置WebSocket 创建一个WebSocket配置类: ```java import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(new LogWebSocketHandler(), "/logs").setAllowedOrigins("*"); } } ``` 创建WebSocket处理程序`LogWebSocketHandler`: ```java import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; import java.io.IOException; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; public class LogWebSocketHandler extends TextWebSocketHandler { private final List<WebSocketSession> sessions = new CopyOnWriteArrayList<>(); @Override public void afterConnectionEstablished(WebSocketSession session) { sessions.add(session); } @Override public void afterConnectionClosed(WebSocketSession session, org.springframework.web.socket.CloseStatus status) { sessions.remove(session); } // 这个方法用于将日志消息发送给所有连接的客户端 public void sendMessageToAll(String message) { for (WebSocketSession session : sessions) { try { if (session.isOpen()) { session.sendMessage(new TextMessage(message)); } } catch (IOException e) { // 处理异常,例如移除session sessions.remove(session); } } } } ``` ### 3. 创建自定义Logback Appender 我们需要一个自定义的Appender,它将日志事件发送到`LogWebSocketHandler`。但是,我们如何将Appender与WebSocketHandler关联起来?因为Appender是Logback初始化的,而WebSocketHandler是Spring管理的,所以我们需要一个桥梁。 我们可以通过单例模式来获取`LogWebSocketHandler`的实例,但更Spring的方式是使用依赖注入。然而,Logback Appender是在Spring上下文初始化之前初始化的,所以不能直接注入。因此,我们使用一个静态的桥接类。 创建一个`LogBroadcaster`类,用于持有`LogWebSocketHandler`的引用(通过Spring设置): ```java public class LogBroadcaster { private static LogWebSocketHandler logWebSocketHandler; public static void setLogWebSocketHandler(LogWebSocketHandler handler) { logWebSocketHandler = handler; } public static void broadcast(String message) { if (logWebSocketHandler != null) { logWebSocketHandler.sendMessageToAll(message); } } } ``` 在Spring配置中,我们需要在`LogWebSocketHandler`被创建后设置到`LogBroadcaster`中。我们可以通过一个`@PostConstruct`方法来实现。 修改`LogWebSocketHandler`,添加一个初始化方法: ```java import javax.annotation.PostConstruct; public class LogWebSocketHandler extends TextWebSocketHandler { // ... 之前的代码 @PostConstruct public void init() { LogBroadcaster.setLogWebSocketHandler(this); } } ``` 但是,注意:`LogWebSocketHandler`是由我们自己new出来的,不是Spring管理的,所以`@PostConstruct`不会生效。因此,我们改为在配置类中设置。 修改`WebSocketConfig`: ```java @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Bean public LogWebSocketHandler logWebSocketHandler() { return new LogWebSocketHandler(); } @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(logWebSocketHandler(), "/logs").setAllowedOrigins("*"); } } ``` 然后,我们可以在配置类中设置`LogBroadcaster`: ```java @Configuration public class AppConfig { @Autowired public void setLogWebSocketHandler(LogWebSocketHandler logWebSocketHandler) { LogBroadcaster.setLogWebSocketHandler(logWebSocketHandler); } } ``` 现在,创建自定义Logback Appender: ```java import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.AppenderBase; public class WebSocketAppender extends AppenderBase<ILoggingEvent> { @Override protected void append(ILoggingEvent event) { // 将日志事件换为字符串(可以格式化) String formattedMessage = event.getFormattedMessage(); // 通过LogBroadcaster广播 LogBroadcaster.broadcast(formattedMessage); } } ``` ### 4. 配置logback.xml 在`resources/logback.xml`中配置我们的appender: ```xml <configuration> <!-- 其他配置 --> <appender name="WEBSOCKET" class="com.example.WebSocketAppender"> <!-- 可以设置一些过滤器或布局 --> </appender> <root level="INFO"> <appender-ref ref="WEBSOCKET" /> <!-- 可以同时保留控制台输出 --> <appender-ref ref="CONSOLE" /> </root> </configuration> ``` ### 5. 前端代码示例 创建一个HTML页面,使用WebSocket连接并显示日志: ```html <!DOCTYPE html> <html> <head> <title>Logs</title> </head> <body> <pre id="log-container"></pre> <script> const socket = new WebSocket('ws://' + window.location.host + '/logs'); const logContainer = document.getElementById('log-container'); socket.onmessage = function(event) { logContainer.textContent += event.data + '\n'; // 滚动到底部 logContainer.scrollTop = logContainer.scrollHeight; }; </script> </body> </html> ``` ### 注意事项: 1. 性能:在高频日志场景下,频繁发送WebSocket消息可能会导致性能问题。可以考虑使用缓冲机制(如每100毫秒发送一批日志)。 2. 安全性:这里允许所有来源(`setAllowedOrigins("*")`),在生产环境中应限制为可信来源。 3. 异常处理:需要处理WebSocket发送失败的情况,例如客户端断开连接。 4. 格式化:在Appender中,我们可以使用更复杂的格式化,例如使用`Layout`来格式化日志事件。 ### 完整代码结构: - `WebSocketConfig`:配置WebSocket - `LogWebSocketHandler`:处理WebSocket连接和消息广播 - `LogBroadcaster`:作为静态桥梁,将日志事件从Appender传递到WebSocketHandler - `WebSocketAppender`:自定义Logback Appender - 前端HTML页面 ### 解释: - 当应用程序启动时,Spring会创建`LogWebSocketHandler`的bean,并通过`AppConfig`将其设置到`LogBroadcaster`中。 - Logback初始化时,会创建`WebSocketAppender`实例,并将其添加到日志上下文中。 - 当日志事件发生时,`WebSocketAppender`的`append`方法被调用,它通过`LogBroadcaster`广播消息。 - `LogBroadcaster`调用`LogWebSocketHandler`的`sendMessageToAll`方法,将消息发送给所有连接的WebSocket客户端。 ### 可能的问题: - 如果日志事件发生在Spring上下文初始化之前,那么`LogBroadcaster`中的`logWebSocketHandler`可能为null,导致日志丢失。这可以通过在Appender中添加重试或缓冲机制来解决,但这里为了简单没有处理。 ### 改进: - 使用异步Appender:Logback提供了`AsyncAppender`,可以将日志事件异步处理,避免阻塞主线程。 现在,我们可以按照这个方案实现。 ### 相关问题的提出: 根据要求,在最后提出相关问题。 §§相关问题§§: 1. 如何在高并发日志输出的情况下优化WebSocket日志推送的性能? 2. 如何确保WebSocket在传输日志时的安全性(比如身份验证)? 3. 除了WebSocket,还有哪些技术可以实现服务器日志的实时推送? 4. 如何在前端对日志进行过滤和搜索?</think>### 通过WebSocket将Logback日志实时推送到前端 以下是完整的解决方案,使用Spring Boot + Logback + WebSocket实现日志实时推送: #### 解决方案步骤: 1. 创建自定义Logback Appender捕获日志 2. 配置WebSocket服务器端 3. 建立前端WebSocket连接 4. 将日志通过WebSocket推送到前端 #### 完整代码实现: ##### 1. 添加Maven依赖 (pom.xml) ```xml <dependencies> <!-- Spring Boot Starter WebSocket --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <!-- Logback --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> </dependency> <!-- Frontend integration (optional) --> <dependency> <groupId>org.webjars</groupId> <artifactId>webjars-locator-core</artifactId> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>sockjs-client</artifactId> <version>1.5.1</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>stomp-websocket</artifactId> <version>2.3.4</version> </dependency> </dependencies> ``` ##### 2. 自定义Logback Appender (WebSocketAppender.java) ```java import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.AppenderBase; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; public class WebSocketAppender extends AppenderBase<ILoggingEvent> { // 线程安全的日志队列 private static final BlockingQueue<String> LOG_QUEUE = new LinkedBlockingQueue<>(1000); @Override protected void append(ILoggingEvent event) { // 格式化日志:时间 + 级别 + 消息 String formattedLog = String.format("[%s] [%s] %s", event.getTimeStamp(), event.getLevel(), event.getFormattedMessage()); // 添加到队列(如果队列满则丢弃最旧的日志) if (!LOG_QUEUE.offer(formattedLog)) { LOG_QUEUE.poll(); // 移除最旧的日志 LOG_QUEUE.offer(formattedLog); } } // 获取日志队列(供WebSocket处理器使用) public static BlockingQueue<String> getLogQueue() { return LOG_QUEUE; } } ``` ##### 3. WebSocket配置类 (WebSocketConfig.java) ```java import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(new LogWebSocketHandler(), "/log-ws") .setAllowedOrigins("*"); // 允许所有来源 } } ``` ##### 4. WebSocket处理器 (LogWebSocketHandler.java) ```java import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; import java.io.IOException; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class LogWebSocketHandler extends TextWebSocketHandler { // 存储所有活动的WebSocket会话 private static final List<WebSocketSession> sessions = new CopyOnWriteArrayList<>(); // 创建定时任务线程池 private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); static { // 每秒检查并发送日志 scheduler.scheduleAtFixedRate(LogWebSocketHandler::sendLogsToClients, 0, 100, TimeUnit.MILLISECONDS); } @Override public void afterConnectionEstablished(WebSocketSession session) { sessions.add(session); } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { sessions.remove(session); } private static void sendLogsToClients() { if (sessions.isEmpty()) return; // 从队列中获取所有可用日志 BlockingQueue<String> logQueue = WebSocketAppender.getLogQueue(); List<String> logs = new ArrayList<>(); logQueue.drainTo(logs); if (logs.isEmpty()) return; // 构建JSON格式的日志消息 String jsonLogs = "{\"logs\": " + new Gson().toJson(logs) + "}"; TextMessage message = new TextMessage(jsonLogs); // 发送给所有连接的客户端 for (WebSocketSession session : sessions) { try { if (session.isOpen()) { session.sendMessage(message); } } catch (IOException e) { // 处理发送失败 } } } } ``` ##### 5. Logback配置文件 (logback-spring.xml) ```xml <configuration> <!-- 控制台Appender --> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <!-- 自定义WebSocket Appender --> <appender name="WEBSOCKET" class="com.example.WebSocketAppender"/> <root level="INFO"> <appender-ref ref="CONSOLE"/> <appender-ref ref="WEBSOCKET"/> </root> </configuration> ``` ##### 6. 前端HTML示例 (log-viewer.html) ```html <!DOCTYPE html> <html> <head> <title>实时日志查看器</title> <style> #log-container { font-family: monospace; background: #1e1e1e; color: #d4d4d4; padding: 20px; height: 80vh; overflow-y: auto; white-space: pre-wrap; } .log-entry { margin: 5px 0; } .log-info { color: #72b372; } .log-warn { color: #e2c08d; } .log-error { color: #f48771; } </style> </head> <body> <h1>实时日志查看器</h1> <div id="log-container"></div> <script> const logContainer = document.getElementById('log-container'); const socket = new WebSocket('ws://' + window.location.host + '/log-ws'); socket.onmessage = function(event) { const data = JSON.parse(event.data); data.logs.forEach(log => { const logEntry = document.createElement('div'); logEntry.className = 'log-entry'; // 根据日志级别添加样式 if (log.includes('[ERROR]')) logEntry.classList.add('log-error'); else if (log.includes('[WARN]')) logEntry.classList.add('log-warn'); else logEntry.classList.add('log-info'); logEntry.textContent = log; logContainer.appendChild(logEntry); }); // 自动滚动到底部 logContainer.scrollTop = logContainer.scrollHeight; }; socket.onclose = function() { logContainer.innerHTML += 'WebSocket连接已关闭\n'; }; </script> </body> </html> ``` #### 关键实现解释: 1. **自定义Logback Appender**: - 继承`AppenderBase`捕获所有日志事件 - 使用线程安全的`BlockingQueue`存储日志(容量1000条) - 当队列满时自动丢弃最旧日志,防止内存溢出 2. **WebSocket消息推送**: - 使用`ScheduledExecutorService`每100毫秒检查一次日志队列 - 批量发送日志(减少WebSocket通信次数) - 采用JSON格式传输数据:`{"logs": ["log1", "log2"]}` 3. **前端实现**: - 建立WebSocket连接`ws://your-domain/log-ws` - 根据日志级别添加CSS样式(INFO/绿色,WARN/黄色,ERROR/红色) - 自动滚动保持最新日志可见 4. **性能优化**: - 使用`CopyOnWriteArrayList`存储会话,避免并发问题 - 批量处理日志减少网络请求 - 设置日志队列上限防止内存泄漏 #### 部署和使用: 1. 启动Spring Boot应用 2. 访问`http://localhost:8080/log-viewer.html` 3. 应用产生的日志将实时显示在页面 4. 支持多客户端同时查看(每个连接独立接收日志) #### 注意事项: 1. **安全性**:生产环境应添加认证机制(如JWT) 2. **性能监控**:在高日志量场景监控队列大小 3. **日志格式化**:可根据需要调整日志格式 4. **错误处理**:添加WebSocket重连机制 5. **浏览器兼容**:旧版浏览器需使用SockJS
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值