浏览代码

完善日志输出

17491 1 小时之前
父节点
当前提交
3c62adaba7

+ 0 - 46
skills/java-client-file-logging/SKILL.md

@@ -1,46 +0,0 @@
----
-name: java-client-file-logging
-description: Add or review lightweight, dependency-free file logging for Java 8 desktop clients. Use when a Swing, JavaFX, or packaged Java client needs daily UTF-8 logs beside its JAR, structured records for login/scan/business events, and explicit user-close or uncaught-exception records without adopting a logging framework.
----
-
-# Java Client File Logging
-
-Use this pattern for a small desktop client where operational support needs inspectable local logs and adding Logback or Log4j is disproportionate. Copy [assets/AppLog.java](assets/AppLog.java), change its package and `APP_NAME`, then integrate it before the UI or worker threads start.
-
-## Integration Workflow
-
-1. Call `AppLog.init()` as the first operation in `main`, before creating Swing/JavaFX components, network clients, or executors.
-2. Mark confirmed operator shutdown immediately before `System.exit(0)` with `AppLog.markUserClose()`.
-3. Log business boundaries rather than incidental control flow:
-   - `login(userId, method, success)` for every completed or rejected login.
-   - `scan(type, code)` immediately after a non-empty scan is accepted.
-   - `work(action, code, success)` after each MES/device/API operation resolves.
-4. Run the packaged JAR from its intended release folder and verify the log appears in `<jar-directory>/logs/<app-name>-yyyy-MM-dd.log`.
-
-Keep user-facing messages and diagnostics separate: log stable actions and identifiers, not display text or request payloads. Never log passwords, tokens, session IDs, or raw sensitive responses.
-
-## Expected Records
-
-The template writes one line per event in this shape:
-
-```text
-[2026-07-14 11:00:05.358][INFO][AWT-EventQueue-0] Login succeeded, method=badge-scan, user=10001
-```
-
-It appends to one file per calendar day, writes UTF-8, includes the thread name, serializes writes with a lock, and flushes each event. The shutdown hook writes either a confirmed user-close record or the uncaught exception and stack trace.
-
-## Placement Rules
-
-- Derive the base directory from the running code source. For a JAR this is its parent folder; in an IDE it falls back to `user.dir`.
-- Use the exact same package/import convention as the target application.
-- Preserve any existing default uncaught-exception handler when the host application or framework owns it. The supplied template installs one because it is intended for standalone clients; chain to the existing handler if one must remain active.
-- Keep `SimpleDateFormat` accesses inside the logging lock, as it is not thread-safe.
-- Treat logging failure as non-fatal. The client must remain usable when the log directory is read-only or unavailable.
-
-## Review Checklist
-
-- Initialization happens before any meaningful client activity.
-- All `System.exit` paths that mean an intentional close call `markUserClose()`.
-- Login, scans, and externally visible operation outcomes are each recorded once.
-- Values passed to log methods cannot inject new lines; the helper normalizes CR/LF.
-- The release layout leaves the JAR directory writable, or the deployment owner accepts that logging is best-effort.

+ 0 - 4
skills/java-client-file-logging/agents/openai.yaml

@@ -1,4 +0,0 @@
-interface:
-  display_name: "Java Client File Logging"
-  short_description: "Add durable file logs to Java desktop clients"
-  default_prompt: "Use $java-client-file-logging to add client-side file logging to this Java desktop application."

+ 0 - 88
skills/java-client-file-logging/assets/AppLog.java

@@ -1,88 +0,0 @@
-package com.example.client.util;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.PrintStream;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-/**
- * Lightweight local logging for a standalone Java desktop client.
- * Change APP_NAME and package before use.
- */
-public final class AppLog {
-    private static final String APP_NAME = "client";
-    private static final Object LOCK = new Object();
-    private static final SimpleDateFormat TIME = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
-    private static PrintStream out;
-    private static volatile String exitReason;
-    private static volatile String uncaughtThread;
-    private static volatile Throwable uncaughtException;
-
-    private AppLog() { }
-
-    public static void init() {
-        synchronized (LOCK) {
-            if (out != null) return;
-            try {
-                File code = new File(AppLog.class.getProtectionDomain().getCodeSource().getLocation().toURI());
-                File base = code.isFile() ? code.getParentFile() : new File(System.getProperty("user.dir"));
-                File dir = new File(base, "logs");
-                if (!dir.exists() && !dir.mkdirs()) return;
-
-                String day = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
-                out = new PrintStream(new FileOutputStream(new File(dir, APP_NAME + "-" + day + ".log"), true), true, "UTF-8");
-                Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
-                    public void uncaughtException(Thread thread, Throwable exception) {
-                        exitReason = "UNCAUGHT_EXCEPTION";
-                        uncaughtThread = thread.getName();
-                        uncaughtException = exception;
-                    }
-                });
-                Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
-                    public void run() {
-                        if ("USER_CLOSE".equals(exitReason)) {
-                            log("INFO", "Client exited, reason=user-close", null);
-                        } else if ("UNCAUGHT_EXCEPTION".equals(exitReason)) {
-                            log("ERROR", "Client exited, reason=uncaught-exception, thread=" + clean(uncaughtThread), uncaughtException);
-                        }
-                    }
-                }, APP_NAME + "-log-shutdown"));
-            } catch (Exception ignored) {
-                // Logging is best-effort and must not stop client startup.
-            }
-        }
-    }
-
-    public static void login(String userId, String method, boolean success) {
-        log(success ? "INFO" : "WARN", "Login " + (success ? "succeeded" : "failed")
-                + ", method=" + clean(method) + ", user=" + clean(userId), null);
-    }
-
-    public static void scan(String type, String code) {
-        log("INFO", "Scan, type=" + clean(type) + ", code=" + clean(code), null);
-    }
-
-    public static void work(String action, String code, boolean success) {
-        log(success ? "INFO" : "WARN", "Operation " + (success ? "succeeded" : "failed")
-                + ", action=" + clean(action) + ", code=" + clean(code), null);
-    }
-
-    public static void markUserClose() {
-        exitReason = "USER_CLOSE";
-    }
-
-    private static String clean(String value) {
-        return value == null ? "" : value.replace('\r', ' ').replace('\n', ' ');
-    }
-
-    private static void log(String level, String message, Throwable exception) {
-        synchronized (LOCK) {
-            if (out == null) return;
-            out.println("[" + TIME.format(new Date()) + "][" + level + "]["
-                    + Thread.currentThread().getName() + "] " + message);
-            if (exception != null) exception.printStackTrace(out);
-            out.flush();
-        }
-    }
-}

+ 0 - 11
src/com/mes/netty/NettyClientHandler.java

@@ -6,23 +6,16 @@ import io.netty.channel.Channel;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.channel.ChannelInboundHandlerAdapter;
 
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
 public class NettyClientHandler extends ChannelInboundHandlerAdapter {
-
-    private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
     public static byte[] responseByte;
 
     @Override
     public void channelActive(ChannelHandlerContext ctx) throws Exception {
-        System.out.println("mes connecting:" + sdf.format(new Date()));
     }
 
     @Override
     public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
         Channel channel = ctx.channel();
-        System.err.println("close tcp, ip:" + channel.remoteAddress());
         MesClient.tcp_connect_flag = false;
         MesClient.setTcpStatus();
         // 关闭通道
@@ -31,7 +24,6 @@ public class NettyClientHandler extends ChannelInboundHandlerAdapter {
 
     @Override
     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
-        System.out.println("msg:"+msg);
 
         String mes_msg = formatResult(msg.toString());
         if(mes_msg == null || mes_msg.isEmpty()){ // error msg
@@ -39,10 +31,8 @@ public class NettyClientHandler extends ChannelInboundHandlerAdapter {
         }
 
         String msg_type = ProtocolParam.getMsgType(mes_msg);
-        System.out.println("msg_type="+msg_type);
 
         String processMsgRet = MesMsgUtils.processMsg(mes_msg, msg_type);
-        System.out.println("processMsgRet="+processMsgRet);
         switch(msg_type) {
             case "AQDW": // 查询质量
                 MesRevice.checkQualityRevice(processMsgRet,mes_msg);
@@ -70,7 +60,6 @@ public class NettyClientHandler extends ChannelInboundHandlerAdapter {
     }
 
     private String formatResult(String msg){
-        System.err.println("length:" + msg.length() + " content:" + msg);
         String msgType = msg.substring(0,2).trim();
         if(msgType.equals("OK")){
             return msg.substring(3,msg.length());

+ 0 - 6
src/com/mes/netty/XDecoder.java

@@ -23,9 +23,6 @@ public class XDecoder extends ByteToMessageDecoder {
      */
     @Override
     protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
-        System.out.println(Thread.currentThread() + "收到了一次数据包,长度是:" + in.readableBytes());
-        String content = hexStringToAscii(ByteBufUtil.hexDump(in));
-        System.out.println("接收包内容:" + hexStringToAscii(ByteBufUtil.hexDump(in)));
 
         // 合并报文
         ByteBuf message = null;
@@ -35,8 +32,6 @@ public class XDecoder extends ByteToMessageDecoder {
             message = Unpooled.buffer();
             message.writeBytes(tempMsg);
             message.writeBytes(in);
-            System.out.println("合并:上一数据包余下的长度为:" + tmpMsgSize + ",合并后长度为:" + message.readableBytes());
-            System.out.println("合并后包内容:" + hexStringToAscii(ByteBufUtil.hexDump(message)));
         } else {
             message = in;
         }
@@ -108,7 +103,6 @@ public class XDecoder extends ByteToMessageDecoder {
         // 第二个报文: 1 与第一次
         size = message.readableBytes();
         if (size != 0) {
-            System.out.println("多余的数据长度:" + size);
             // 剩下来的数据放到tempMsg暂存
             tempMsg.clear();
             tempMsg.writeBytes(message.readBytes(size));

+ 4 - 6
src/com/mes/ui/LoginFarme.java

@@ -33,8 +33,10 @@ public class LoginFarme extends JFrame {
         addWindowListener(new WindowAdapter() {
             @Override
             public void windowClosing(WindowEvent e) {
-                AppLog.markUserClose();
-                System.exit(0);
+                if (MesClient.confirmClose(LoginFarme.this)) {
+                    AppLog.markUserClose();
+                    System.exit(0);
+                }
             }
         });
         JLabel imgLabel = new JLabel(bg);//将背景图放在标签里。
@@ -111,7 +113,6 @@ public class LoginFarme extends JFrame {
         }
         String username = Base64Utils.getBase64(user_str);
         String password = Base64Utils.getBase64(password_str);
-        System.out.println("&username=" + username + "&password=" + password);
         String url = "http://"+MesClient.mes_server_ip+":8980/js/a/login?__ajax=json&username="+username+"&password="+password+"&validCode=&__sid=";
         String loginResult = HttpUtils.sendRequest(url);
         if(loginResult.equalsIgnoreCase("false")) {
@@ -137,12 +138,10 @@ public class LoginFarme extends JFrame {
         //userNameTxt.setText("");
         //userPasswordTxt.setText("");
         String scanContent = JOptionPane.showInputDialog(null, "请扫码工牌二维码");
-        System.out.println("scanContent="+scanContent);
         if(scanContent!=null&&!scanContent.equalsIgnoreCase("")) {
             AppLog.scan("badge", scanContent);
             String url = "http://"+MesClient.mes_server_ip+":8980/js/a/mes/mesLogin/login?__login=true&__ajax=json&username="+scanContent;
             String loginResult = HttpUtils.sendRequest(url);
-            System.out.println("loginResult="+loginResult);
             if(loginResult.equalsIgnoreCase("false")) {
                 AppLog.login(scanContent, "badge-scan", false);
                 JOptionPane.showMessageDialog(MesClient.mesClientFrame,"登录异常,请检查网络或联系网络管理员","提示窗口", JOptionPane.INFORMATION_MESSAGE);
@@ -177,7 +176,6 @@ public class LoginFarme extends JFrame {
             //请求权限
             String url_authority = "http://"+MesClient.mes_server_ip+":8980/js/a/mes/mesLineProcess/userAuth?__ajax=json&type=0&__sid="+MesClient.sessionid+"&oprno="+MesClient.mes_gw+"&lineSn="+MesClient.mes_line_sn;
             String authorityResult = HttpUtils.sendRequest(url_authority);
-            System.out.println("authorityResult="+authorityResult);
             JSONObject authorityObj = JSONObject.parseObject(authorityResult);
             if(authorityObj.get("result")!=null&&authorityObj.get("result").toString().equalsIgnoreCase("true")) {
                 JSONObject authObjTmp = JSONObject.parseObject(authorityObj.get("data").toString());

+ 14 - 17
src/com/mes/ui/MesClient.java

@@ -15,8 +15,6 @@ import com.mes.util.DateLocalUtils;
 import com.mes.util.HttpUtils;
 import com.mes.util.JdbcUtils;
 import javafx.embed.swing.JFXPanel;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import javax.swing.*;
 import javax.swing.border.EmptyBorder;
@@ -47,7 +45,6 @@ import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
 public class MesClient extends JFrame {
-    public static final Logger log = LoggerFactory.getLogger(MesClient.class);
     private static String Drivde = "org.sqlite.JDBC";
 
     public static int mes_auth = 0;
@@ -199,7 +196,7 @@ public class MesClient extends JFrame {
                     textFocus();
 
                 }catch (Exception e){
-                    e.printStackTrace();
+                    AppLog.error("客户端启动失败", e);
                 }
             }
         });
@@ -217,7 +214,6 @@ public class MesClient extends JFrame {
             public void run() {
                 try{
                     List<CmtReq> prods = JdbcUtils.getCmtParams();
-                    System.out.println("prods:"+ JSON.toJSONString(prods));
                     if(prods.size() > 0){
                         // 定时上传参数
                         JSONObject retObj = DataUtil.upParams(JSON.toJSONString(prods));
@@ -283,7 +279,6 @@ public class MesClient extends JFrame {
                 if(!page.equals(lastDetectedPage)){
                     lastDetectedPage = page;
                     PlcUtil.changeEnable(s7PLC, false);
-                    System.out.println("识别到当前面:" + page + ",已发送禁用信号");
                 }
             }
         }, 1000,1000);
@@ -324,7 +319,6 @@ public class MesClient extends JFrame {
 
         mes_gwflag = pro.getProperty("mes.gwflag");
 
-        System.out.println(mes_gw + ";" + mes_gw_des + ";" + mes_server_ip + ";" + mes_tcp_port + ";" + mes_heart_beat_cycle);
     }
 
     // 閸掓繂顫愰崠鏈P
@@ -533,6 +527,7 @@ public class MesClient extends JFrame {
     }
 
     public static void logoff() {
+        AppLog.logout(user_menu == null ? "" : user_menu.getText());
         welcomeWin.setVisible(true);
         mesClientFrame.setVisible(false);
 
@@ -547,6 +542,11 @@ public class MesClient extends JFrame {
         connect_request_flag = false;
     }
 
+    public static boolean confirmClose(Component parent) {
+        return JOptionPane.showConfirmDialog(parent, "确定要关闭MES系统客户端吗?", "确认关闭",
+                JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION;
+    }
+
     public MesClient() {
         setIconImage(Toolkit.getDefaultToolkit().getImage(MesClient.class.getResource("/bg/logo.png")));
         setTitle("MES系统客户端:"+mes_gw + "- " + mes_gw_des);
@@ -554,8 +554,10 @@ public class MesClient extends JFrame {
         addWindowListener(new WindowAdapter() {
             @Override
             public void windowClosing(WindowEvent e) {
-                AppLog.markUserClose();
-                System.exit(0);
+                if (confirmClose(MesClient.this)) {
+                    AppLog.markUserClose();
+                    System.exit(0);
+                }
             }
         });
         setBounds(0, 0, 1024, 768);
@@ -1168,7 +1170,6 @@ public class MesClient extends JFrame {
             public void stateChanged(ChangeEvent e) {
                 JTabbedPane tabbedPane = (JTabbedPane) e.getSource();
                 int selectedIndex = tabbedPane.getSelectedIndex();
-                System.out.println("selectedIndex:"+selectedIndex);
 
                 if(selectedIndex == 1){
 
@@ -1445,7 +1446,6 @@ public class MesClient extends JFrame {
         ExecutorService executorService = Executors.newSingleThreadExecutor();
         // 提交任务
         executorService.submit(() -> {
-            log.info("异步任务执行中...");
             // 模拟耗时操作
             try {
                 while (true){
@@ -1467,9 +1467,7 @@ public class MesClient extends JFrame {
                             String bw = rs.getString("bw");
                             // 发送请求
                             String url = "http://"+MesClient.mes_server_ip+":8980/js/a/mes/mesProductRecord/updateQualityByTiming";
-                            log.info("质量:"+bw);
                             String s = HttpUtils.sendPostRequestJson(url, bw );
-                            log.info("结果:"+s);
                             Boolean result = JSONObject.parseObject(s).getBoolean("result");
                             if(result) {
                                 // 更改状态为 1
@@ -1479,18 +1477,17 @@ public class MesClient extends JFrame {
                         rs.close();
                         statement.close();
                     } catch (Exception e) {
-                        log.info(e.getMessage());
+                        AppLog.error("质量上传任务失败", e);
                     }
                     try {
                         Thread.sleep(2000);
                     }catch (Exception e){
-                        log.info(e.getMessage());
+                        AppLog.error("质量上传任务被中断", e);
                     }
                 }
             } catch (Exception e) {
-                log.info(e.getMessage());
+                AppLog.error("质量上传任务停止", e);
             }
-            log.info("异步任务执行完毕");
         });
     }
 }

+ 5 - 5
src/com/mes/ui/MesRevice.java

@@ -73,7 +73,7 @@ public class MesRevice {
 
             }
         }catch (Exception e){
-            e.printStackTrace();
+            AppLog.error("质量查询回复处理失败", e);
         }
     }
 
@@ -84,7 +84,7 @@ public class MesRevice {
 
             }
         }catch (Exception e){
-            e.printStackTrace();
+            AppLog.error("开始回复处理失败", e);
         }
     }
 
@@ -97,7 +97,7 @@ public class MesRevice {
 
             }
         }catch (Exception e){
-            e.printStackTrace();
+            AppLog.error("绑定回复处理失败", e);
         }
     }
 
@@ -110,7 +110,7 @@ public class MesRevice {
 
             }
         }catch (Exception e){
-            e.printStackTrace();
+            AppLog.error("解绑回复处理失败", e);
         }
     }
 
@@ -137,7 +137,7 @@ public class MesRevice {
                 }
             }
         }catch (Exception e){
-            e.printStackTrace();
+            AppLog.error("质量结果回复处理失败", e);
         }
     }
 }

+ 104 - 15
src/com/mes/util/AppLog.java

@@ -1,10 +1,16 @@
 package com.mes.util;
 
 import java.io.File;
-import java.io.FileOutputStream;
 import java.io.PrintStream;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
 import java.text.SimpleDateFormat;
 import java.util.Date;
+import java.util.Properties;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
 
 /** Lightweight local logging for the standalone MES desktop client. */
 public final class AppLog {
@@ -12,6 +18,9 @@ public final class AppLog {
     private static final Object LOCK = new Object();
     private static final SimpleDateFormat TIME = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
     private static PrintStream out;
+    private static File stateFile;
+    private static ScheduledExecutorService heartbeat;
+    private static volatile boolean userClose;
     private static volatile String exitReason;
     private static volatile String uncaughtThread;
     private static volatile Throwable uncaughtException;
@@ -27,24 +36,43 @@ public final class AppLog {
                 File dir = new File(base, "logs");
                 if (!dir.exists() && !dir.mkdirs()) return;
 
+                stateFile = new File(dir, APP_NAME + ".running");
                 String day = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
                 out = new PrintStream(new FileOutputStream(new File(dir, APP_NAME + "-" + day + ".log"), true), true, "UTF-8");
+                reportPreviousRun();
+                writeState();
                 Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
                     public void uncaughtException(Thread thread, Throwable exception) {
-                        exitReason = "UNCAUGHT_EXCEPTION";
-                        uncaughtThread = thread.getName();
-                        uncaughtException = exception;
+                        synchronized (LOCK) {
+                            exitReason = "UNCAUGHT_EXCEPTION";
+                            uncaughtThread = thread.getName();
+                            uncaughtException = exception;
+                            log("错误", "未捕获异常,线程=" + clean(uncaughtThread), exception);
+                        }
                     }
                 });
                 Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
                     public void run() {
-                        if ("USER_CLOSE".equals(exitReason)) {
-                            log("INFO", "Client exited, reason=user-close", null);
-                        } else if ("UNCAUGHT_EXCEPTION".equals(exitReason)) {
-                            log("ERROR", "Client exited, reason=uncaught-exception, thread=" + clean(uncaughtThread), uncaughtException);
+                        synchronized (LOCK) {
+                            if (userClose) {
+                            log("信息", "客户端退出,原因=用户主动正常关闭,状态文件将删除", null);
+                            deleteState();
+                            } else if ("UNCAUGHT_EXCEPTION".equals(exitReason)) {
+                            log("错误", "客户端退出,原因=未捕获异常,线程=" + clean(uncaughtThread), uncaughtException);
+                            } else {
+                                log("错误", "客户端退出,原因=未收到正常关闭标记(可能被任务管理器终止、进程崩溃或系统终止)", null);
+                            }
                         }
                     }
                 }, APP_NAME + "-log-shutdown"));
+                heartbeat = Executors.newSingleThreadScheduledExecutor(r -> {
+                    Thread thread = new Thread(r, APP_NAME + "-heartbeat");
+                    thread.setDaemon(true);
+                    return thread;
+                });
+                heartbeat.scheduleAtFixedRate(() -> {
+                    synchronized (LOCK) { writeState(); }
+                }, 5, 5, TimeUnit.SECONDS);
             } catch (Exception ignored) {
                 // Logging is best-effort and must not stop client startup.
             }
@@ -52,21 +80,79 @@ public final class AppLog {
     }
 
     public static void login(String userId, String method, boolean success) {
-        log(success ? "INFO" : "WARN", "Login " + (success ? "succeeded" : "failed")
-                + ", method=" + clean(method) + ", user=" + clean(userId), null);
+        log(success ? "信息" : "警告", "登录" + (success ? "成功" : "失败")
+                + ",方式=" + clean(method) + ",用户=" + clean(userId), null);
+    }
+
+    public static void logout(String userId) {
+        log("信息", "用户退出,用户=" + clean(userId), null);
     }
 
     public static void scan(String type, String code) {
-        log("INFO", "Scan, type=" + clean(type) + ", code=" + clean(code), null);
+        log("信息", "扫码,类型=" + clean(type) + ",内容=" + clean(code), null);
     }
 
     public static void work(String action, String code, boolean success) {
-        log(success ? "INFO" : "WARN", "Operation " + (success ? "succeeded" : "failed")
-                + ", action=" + clean(action) + ", code=" + clean(code), null);
+        log(success ? "信息" : "警告", "操作" + (success ? "成功" : "失败")
+                + ",动作=" + clean(action) + ",内容=" + clean(code), null);
     }
 
     public static void markUserClose() {
-        exitReason = "USER_CLOSE";
+        synchronized (LOCK) {
+            if (userClose) return;
+            userClose = true;
+            exitReason = "USER_CLOSE";
+            log("信息", "收到用户主动关闭标记", null);
+            writeState();
+        }
+    }
+
+    public static void error(String message, Throwable exception) {
+        log("错误", message, exception);
+    }
+
+    private static void reportPreviousRun() {
+        if (stateFile == null || !stateFile.isFile()) return;
+        Properties state = new Properties();
+        try (FileInputStream input = new FileInputStream(stateFile)) {
+            state.load(input);
+            log("错误", "检测到上次进程未正常关闭(可能被任务管理器结束),进程号="
+                    + clean(state.getProperty("pid")) + ",最后心跳="
+                    + clean(state.getProperty("heartbeat")) + ",状态="
+                    + clean(state.getProperty("status")), null);
+        } catch (Exception exception) {
+            log("警告", "无法读取上次运行状态", exception);
+        }
+    }
+
+    private static void writeState() {
+        if (stateFile == null) return;
+        Properties state = new Properties();
+        state.setProperty("pid", getPid());
+        state.setProperty("heartbeat", Long.toString(System.currentTimeMillis()));
+        state.setProperty("status", "RUNNING");
+        File temporary = new File(stateFile.getParentFile(), stateFile.getName() + ".tmp");
+        try (FileOutputStream output = new FileOutputStream(temporary)) {
+            state.store(output, "MES client running state");
+        } catch (IOException ignored) {
+            return;
+        }
+        if (!temporary.renameTo(stateFile)) {
+            temporary.delete();
+        }
+    }
+
+    private static void deleteState() {
+        if (heartbeat != null) heartbeat.shutdownNow();
+        if (stateFile != null && stateFile.isFile() && !stateFile.delete()) {
+            log("警告", "无法删除运行状态文件", null);
+        }
+    }
+
+    private static String getPid() {
+        String runtime = java.lang.management.ManagementFactory.getRuntimeMXBean().getName();
+        int separator = runtime.indexOf('@');
+        return separator > 0 ? runtime.substring(0, separator) : runtime;
     }
 
     private static String clean(String value) {
@@ -76,9 +162,12 @@ public final class AppLog {
     private static void log(String level, String message, Throwable exception) {
         synchronized (LOCK) {
             if (out == null) return;
+            if (exception != null) {
+                message += ",异常类型=" + exception.getClass().getName()
+                        + ",详情=" + clean(exception.getMessage());
+            }
             out.println("[" + TIME.format(new Date()) + "][" + level + "]["
                     + Thread.currentThread().getName() + "] " + message);
-            if (exception != null) exception.printStackTrace(out);
             out.flush();
         }
     }

+ 0 - 6
src/com/mes/util/HttpUtils.java

@@ -16,7 +16,6 @@ public class HttpUtils {
 		String requestType = "POST";
         //根据接收内容返回数据结果
     	String ret = "";
-    	System.out.println(urlParam);
         HttpURLConnection con = null;
         BufferedReader buffer = null;
         StringBuffer resultBuffer = null;
@@ -36,7 +35,6 @@ public class HttpUtils {
             con.setUseCaches(false);
             //得到响应码
             int responseCode = con.getResponseCode();
-            System.out.println("responseCode="+responseCode);
             if(responseCode == HttpURLConnection.HTTP_OK){
                 //得到响应流
                 InputStream inputStream = con.getInputStream();
@@ -47,7 +45,6 @@ public class HttpUtils {
                 while ((line = buffer.readLine()) != null) {
                     resultBuffer.append(line);
                 }
-                System.out.println(resultBuffer.toString());
                 ret = resultBuffer.toString();
             }else {
             	//ret = String.valueOf(responseCode);
@@ -69,7 +66,6 @@ public class HttpUtils {
         String requestType = "POST";
         //根据接收内容返回数据结果
         String ret = "";
-        System.out.println(urlParam);
         HttpURLConnection con = null;
         BufferedReader buffer = null;
         StringBuffer resultBuffer = null;
@@ -95,7 +91,6 @@ public class HttpUtils {
 
             //得到响应码
             int responseCode = con.getResponseCode();
-            System.out.println("responseCode="+responseCode);
             if(responseCode == HttpURLConnection.HTTP_OK){
                 //得到响应流
                 InputStream inputStream = con.getInputStream();
@@ -106,7 +101,6 @@ public class HttpUtils {
                 while ((line = buffer.readLine()) != null) {
                     resultBuffer.append(line);
                 }
-                System.out.println(resultBuffer.toString());
                 ret = resultBuffer.toString();
             }else {
                 //ret = String.valueOf(responseCode);