在软件开发中,代码的迭代优化往往从提升可维护性、可追踪性入手。本文将详细解析新增的日志系统改进,以及这些改进如何提升系统的实用性和可调试性。

一、代码整体背景

代码实现了一个基于 TF-IDF 算法的问答系统,核心功能包括:

  • 加载训练数据(training_data.txt)构建问答库
  • 提取中英文关键词(支持 GBK 编码中文处理)
  • 通过精确匹配和 TF-IDF 相似度计算返回最佳答案
  • 支持基础交互命令(help/topics/exit等)

其中,改进版在原版本的基础上,重点新增了日志记录功能,下面详细解析具体改进点。

二、核心改进点:新增日志系统

1. 日志相关头文件与常量定义

代码新增了日志功能所需的头文件和常量:

#include <ctime>  // 用于日志时间戳
// 日志文件名
const string LOG_FILE = "chat_log.txt";
  • 引入<ctime>库用于获取当前时间,为日志添加时间戳
  • 定义LOG_FILE常量指定日志文件名(chat_log.txt),便于统一管理日志存储路径

2. 时间戳生成函数:getCurrentTime()

为了让日志具备时间维度的可追溯性,改进版新增了时间戳生成函数:

// 获取当前时间字符串(格式: YYYY-MM-DD HH:MM:SS)
string getCurrentTime() {time_t now = time(NULL);struct tm* localTime = localtime(&now);char timeStr[20];sprintf(timeStr, "%04d-%02d-%02d %02d:%02d:%02d",localTime->tm_year + 1900,  // 年份转换(tm_year为从1900开始的偏移量)localTime->tm_mon + 1,      // 月份转换(0-11 → 1-12)localTime->tm_mday,localTime->tm_hour,localTime->tm_min,localTime->tm_sec);return string(timeStr);
}
  • 功能:生成YYYY-MM-DD HH:MM:SS格式的时间字符串,确保日志记录的时间精确到秒
  • 优势:统一的时间格式便于后续日志分析(如按时间筛选用户交互记录)

3. 日志写入函数:writeLog()

新增了日志写入核心函数,负责将信息追加到日志文件:

// 写入日志信息
void writeLog(const string& type, const string& content) {ofstream logFile(LOG_FILE.c_str(), ios::app);  // 追加模式打开if (logFile.is_open()) {logFile << "[" << getCurrentTime() << "] [" << type << "] " << content << endl;logFile.close();} else {cerr << "警告: 无法打开日志文件 " << LOG_FILE << endl;}
}
  • 关键参数:
    • type:日志类型(如 "系统"/"用户命令"/"用户输入"/"系统响应"),用于分类日志
    • content:日志具体内容
  • 实现细节:
    • 使用ios::app模式打开文件,确保新日志追加到文件末尾(不覆盖历史记录)
    • 日志格式:[时间戳] [类型] 内容,结构清晰,便于阅读和解析

4. 关键节点日志记录

改进版在程序运行的关键节点添加了日志记录,覆盖系统生命周期和用户交互的全流程:

场景日志记录代码作用
程序启动writeLog("系统", "程序启动");记录系统初始化时间,用于排查启动故障
训练数据加载完成sprintf(logMsg, "加载训练数据完成,共%d条记录", exactAnswers.size()); writeLog("系统", logMsg);记录数据加载结果,验证数据是否正确加载
用户输入命令(help)writeLog("用户命令", "输入help,查看帮助信息");追踪用户使用帮助命令的行为
用户输入命令(topics)writeLog("用户命令", "输入topics,查看可回答话题");分析用户对话题的关注度
用户输入空内容writeLog("用户输入", "空输入");统计无效输入情况,优化交互提示
用户输入问题writeLog("用户输入", "问题: " + input);记录用户原始问题,用于后续优化问答库
系统返回答案writeLog("系统响应", "精确匹配回答: " + it->second); 或 writeLog("系统响应", "TF-IDF匹配回答: " + bestAnswer);关联用户问题与系统答案,分析匹配准确性
程序退出writeLog("系统", "用户输入exit,程序退出");记录系统终止时间和原因

三、改进带来的核心价值

  1. 可追溯性提升
    日志记录了系统从启动到退出的全流程状态,以及用户的每一次交互(输入内容、执行命令),当系统出现异常时,可通过日志快速定位问题节点(如数据加载失败、匹配逻辑错误等)。

  2. 用户行为分析
    通过用户输入日志(问题、命令),可以统计高频问题、用户关注的话题等,为优化问答库(补充热门问题答案)提供数据支持。

  3. 系统调试效率提升
    无需通过cout打印临时调试信息,日志文件可永久保存,便于复现问题和对比不同版本的运行差异。

  4. 审计与合规
    对于需要留存交互记录的场景(如简单的客服系统),日志可作为合规审计的依据。

代码 

#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include <vector>
#include <cctype>
#include <cmath>
#include <algorithm>
#include <set>
#include <ctime>  // 用于日志时间戳
using namespace std;// 日志文件名
const string LOG_FILE = "chat_log.txt";// 获取当前时间字符串(格式: YYYY-MM-DD HH:MM:SS)
string getCurrentTime() {time_t now = time(NULL);struct tm* localTime = localtime(&now);char timeStr[20];sprintf(timeStr, "%04d-%02d-%02d %02d:%02d:%02d",localTime->tm_year + 1900,localTime->tm_mon + 1,localTime->tm_mday,localTime->tm_hour,localTime->tm_min,localTime->tm_sec);return string(timeStr);
}// 写入日志信息
void writeLog(const string& type, const string& content) {ofstream logFile(LOG_FILE.c_str(), ios::app);  // 追加模式打开if (logFile.is_open()) {logFile << "[" << getCurrentTime() << "] [" << type << "] " << content << endl;logFile.close();} else {cerr << "警告: 无法打开日志文件 " << LOG_FILE << endl;}
}// 判断是否为中文标点符号(GBK编码)
bool isChinesePunctuation(unsigned char c1, unsigned char c2) {if ((c1 == 0xA1 && (c2 >= 0xA2 && c2 <= 0xAF)) ||  (c1 == 0xA3 && (c2 == 0xAC || c2 == 0xAD)) ||  (c1 == 0xBC && (c2 >= 0x80 && c2 <= 0x8F))) {  return true;}return false;
}// 将字符串转换为小写(仅处理ASCII字符)
string toLower(const string& str) {string result = str;for (size_t i = 0; i < result.length(); ++i) {result[i] = tolower(static_cast<unsigned char>(result[i]));}return result;
}// 从字符串中提取关键词(修复中文处理)
vector<string> extractKeywords(const string& text) {vector<string> keywords;string asciiWord;  // 存储英文/数字词for (size_t i = 0; i < text.length(); ) {unsigned char c = static_cast<unsigned char>(text[i]);// 处理ASCII字符(0-127)if (c <= 127) {if (isalnum(c)) {  // 字母或数字asciiWord += text[i];++i;} else {  // ASCII标点或空格,作为分隔符if (!asciiWord.empty()) {keywords.push_back(toLower(asciiWord));asciiWord.clear();}++i;}} // 处理中文字符(GBK编码,2字节)else {if (i + 1 >= text.length()) {++i;continue;}unsigned char c2 = static_cast<unsigned char>(text[i+1]);// 过滤中文标点if (isChinesePunctuation(c, c2)) {if (!asciiWord.empty()) {keywords.push_back(toLower(asciiWord));asciiWord.clear();}i += 2;continue;}// 提取单个汉字作为关键词string chineseChar;chineseChar += text[i];chineseChar += text[i+1];keywords.push_back(chineseChar);i += 2;}}// 处理剩余的ASCII词if (!asciiWord.empty()) {keywords.push_back(toLower(asciiWord));}return keywords;
}// 显示帮助信息
void showHelp() {cout << "\n===== 使用帮助 =====" << endl;cout << "1. 直接输入您的问题,我会尽力为您解答" << endl;cout << "2. 输入 'exit' 或 'quit' 结束对话" << endl;cout << "3. 输入 'help' 查看帮助信息" << endl;cout << "4. 输入 'topics' 查看我能回答的问题类型" << endl;cout << "====================\n" << endl;
}// 显示可回答的话题类型
void showTopics(const map<string, string>& exactAnswers) {if (exactAnswers.empty()) {cout << "暂无可用的话题信息" << endl;return;}cout << "\n===== 我可以回答这些类型的问题 =====" << endl;int count = 0;for (map<string, string>::const_iterator it = exactAnswers.begin(); it != exactAnswers.end() && count < 5; ++it, ++count) {string sample = it->first;if (sample.length() > 30) {sample = sample.substr(0, 30) + "...";}cout << "- " << sample << endl;}if (exactAnswers.size() > 5) {cout << "... 还有 " << (exactAnswers.size() - 5) << " 个其他话题" << endl;}cout << "=================================\n" << endl;
}// 计算TF-IDF并返回最佳匹配答案
string getBestAnswerByTFIDF(const vector<string>& userKeywords,const map<string, vector<string> >& qas,const map<string, vector<string> >& questionKeywords,const map<string, double>& idfValues) {map<string, double> userTFIDF;for (vector<string>::const_iterator kit = userKeywords.begin(); kit != userKeywords.end(); ++kit) {const string& keyword = *kit;double tf = 0.0;for (vector<string>::const_iterator it = userKeywords.begin(); it != userKeywords.end(); ++it) {if (*it == keyword) tf++;}tf /= userKeywords.size();double idf = 0.0;map<string, double>::const_iterator idfIt = idfValues.find(keyword);if (idfIt != idfValues.end()) {idf = idfIt->second;}userTFIDF[keyword] = tf * idf;}map<string, double> similarityScores;for (map<string, vector<string> >::const_iterator pit = questionKeywords.begin(); pit != questionKeywords.end(); ++pit) {const string& question = pit->first;const vector<string>& keywords = pit->second;map<string, double> questionTFIDF;for (vector<string>::const_iterator kit = keywords.begin(); kit != keywords.end(); ++kit) {const string& keyword = *kit;double tf = 0.0;for (vector<string>::const_iterator it = keywords.begin(); it != keywords.end(); ++it) {if (*it == keyword) tf++;}tf /= keywords.size();double idf = 0.0;map<string, double>::const_iterator idfIt = idfValues.find(keyword);if (idfIt != idfValues.end()) {idf = idfIt->second;}questionTFIDF[keyword] = tf * idf;}double dotProduct = 0.0;double userNorm = 0.0;double questionNorm = 0.0;for (map<string, double>::const_iterator uit = userTFIDF.begin(); uit != userTFIDF.end(); ++uit) {const string& keyword = uit->first;double userWeight = uit->second;userNorm += userWeight * userWeight;map<string, double>::const_iterator qit = questionTFIDF.find(keyword);if (qit != questionTFIDF.end()) {dotProduct += userWeight * qit->second;}}for (map<string, double>::const_iterator qit = questionTFIDF.begin(); qit != questionTFIDF.end(); ++qit) {questionNorm += qit->second * qit->second;}userNorm = sqrt(userNorm);questionNorm = sqrt(questionNorm);double similarity = 0.0;if (userNorm > 0 && questionNorm > 0) {similarity = dotProduct / (userNorm * questionNorm);}similarityScores[question] = similarity;}string bestQuestion;double maxSimilarity = 0.0;for (map<string, double>::const_iterator it = similarityScores.begin(); it != similarityScores.end(); ++it) {if (it->second > maxSimilarity) {maxSimilarity = it->second;bestQuestion = it->first;}}if (maxSimilarity >= 0.15) { map<string, vector<string> >::const_iterator ansIt = qas.find(bestQuestion);if (ansIt != qas.end() && !ansIt->second.empty()) {return ansIt->second[0];}}return "";
}int main() {// 初始化日志writeLog("系统", "程序启动");map<string, string> exactAnswers;map<string, vector<string> > qas;map<string, vector<string> > questionKeywords;map<string, int> documentFrequency;// 打开训练文件ifstream trainingFile("training_data.txt");if (trainingFile.is_open()) {string line;string question = "";bool readingAnswer = false;int totalDocuments = 0;while (getline(trainingFile, line)) {if (line.empty()) {question = "";readingAnswer = false;continue;}if (line.size() >= 2 && line.substr(0, 2) == "Q:") {question = line.substr(2);readingAnswer = false;totalDocuments++;}else if (line.size() >= 2 && line.substr(0, 2) == "A:") {if (!question.empty()) {string answer = line.substr(2);exactAnswers[question] = answer;qas[question].push_back(answer);vector<string> keywords = extractKeywords(question);questionKeywords[question] = keywords;set<string> uniqueKeywords;for (vector<string>::const_iterator it = keywords.begin(); it != keywords.end(); ++it) {uniqueKeywords.insert(*it);}for (set<string>::const_iterator it = uniqueKeywords.begin(); it != uniqueKeywords.end(); ++it) {documentFrequency[*it]++;}}readingAnswer = true;}else if (readingAnswer && !question.empty()) {exactAnswers[question] += "\n" + line;qas[question].back() += "\n" + line;}}trainingFile.close();cout << "已加载 " << exactAnswers.size() << " 条训练数据" << endl;// 记录训练数据加载情况char logMsg[100];sprintf(logMsg, "加载训练数据完成,共%d条记录", exactAnswers.size());writeLog("系统", logMsg);map<string, double> idfValues;for (map<string, int>::const_iterator it = documentFrequency.begin(); it != documentFrequency.end(); ++it) {const string& keyword = it->first;int df = it->second;double idf = log(static_cast<double>(totalDocuments) / (df + 1)) + 1;idfValues[keyword] = idf;}cout << "\n=================================" << endl;cout << "欢迎使用问答系统!我可以回答您的问题" << endl;cout << "输入 'help' 查看可用命令,'exit' 退出程序" << endl;cout << "=================================\n" << endl;string input;while (true) {cout << "请输入您的问题: ";getline(cin, input);if (input == "exit" || input == "quit") {cout << "机器人: 再见!感谢使用!" << endl;writeLog("系统", "用户输入exit,程序退出");break;}else if (input == "help") {showHelp();writeLog("用户命令", "输入help,查看帮助信息");continue;}else if (input == "topics") {showTopics(exactAnswers);writeLog("用户命令", "输入topics,查看可回答话题");continue;}else if (input.empty()) {cout << "机器人: 您的输入为空,请重新输入" << endl;writeLog("用户输入", "空输入");continue;}// 记录用户输入writeLog("用户输入", "问题: " + input);// 精确匹配尝试string inputClean = input;vector<string> inputKeywords = extractKeywords(input);inputClean = "";for (vector<string>::const_iterator it = inputKeywords.begin(); it != inputKeywords.end(); ++it) {inputClean += *it;}bool exactFound = false;for (map<string, string>::const_iterator it = exactAnswers.begin(); it != exactAnswers.end(); ++it) {string questionClean = "";vector<string> qKeywords = extractKeywords(it->first);for (vector<string>::const_iterator qit = qKeywords.begin(); qit != qKeywords.end(); ++qit) {questionClean += *qit;}if (questionClean == inputClean) {cout << "机器人: " << it->second << endl;writeLog("系统响应", "精确匹配回答: " + it->second);exactFound = true;break;}}if (exactFound) {continue;}// 关键词匹配string bestAnswer = getBestAnswerByTFIDF(inputKeywords, qas, questionKeywords, idfValues);if (!bestAnswer.empty()) {cout << "机器人: " << bestAnswer << endl;writeLog("系统响应", "TF-IDF匹配回答: " + bestAnswer);continue;}cout << "机器人: 抱歉,我不太理解这个问题。" << endl;cout << "您可以尝试输入 'topics' 查看我能回答的问题类型" << endl;writeLog("系统响应", "无法匹配到合适回答");}} else {cout << "无法打开训练文件 training_data.txt,请确保文件存在且路径正确" << endl;writeLog("错误", "无法打开训练文件 training_data.txt");return 1;}return 0;
}

四、总结

本次改进的核心是新增了结构化日志系统,通过在关键节点记录时间戳、事件类型和具体内容,显著提升了问答系统的可维护性和可分析性。这种改进思路具有通用性 —— 对于任何需要长期运行或涉及用户交互的程序,添加日志系统都是低成本高收益的优化手段。

 

后续可基于此日志系统进一步扩展,例如:添加日志级别(INFO/WARN/ERROR)、实现日志文件按日期分割(避免单文件过大)、或通过日志分析自动优化 TF-IDF 的匹配阈值等。

注:本文使用豆包辅助编写

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/90754.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/90754.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/90754.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

visual studio2022编译unreal engine5.4.4源码

UE5系列文章目录 文章目录 UE5系列文章目录 前言 一、ue5官网 二.编译源码中遇到的问题 前言 一、ue5官网 UE5官网 UE5源码下载地址 这样虽然下载比较快,但是不能进行代码git管理,以后如何虚幻官方有大的版本变动需要重新下载源码,所以我们还是最好需要visual studio2022…

vulhub Earth靶场攻略

靶场下载 下载链接&#xff1a;https://download.vulnhub.com/theplanets/Earth.ova 靶场使用 将压缩包解压到一个文件夹中&#xff0c;右键&#xff0c;用虚拟机打开&#xff0c;就创建成功了&#xff0c;然后启动虚拟机&#xff1a; 这时候靶场已经启动了&#xff0c;咱们现…

Python训练Day24

浙大疏锦行 元组可迭代对象os模块

Spring核心:Bean生命周期、外部化配置与组件扫描深度解析

Bean生命周期 说明 程序中的每个对象都有生命周期&#xff0c;对象的创建、初始化、应用、销毁的整个过程称之为对象的生命周期&#xff1b; 在对象创建以后需要初始化&#xff0c;应用完成以后需要销毁时执行的一些方法&#xff0c;可以称之为是生命周期方法&#xff1b; 在sp…

日语学习-日语知识点小记-进阶-JLPT-真题训练-N1阶段(1):2017年12月-JLPT-N1

日语学习-日语知识点小记-进阶-JLPT-真题训练-N1阶段&#xff08;1&#xff09;&#xff1a;2017年12月-JLPT-N1 1、前言&#xff08;1&#xff09;情况说明&#xff08;2&#xff09;工程师的信仰&#xff08;3&#xff09;真题训练2、真题-2017年12月-JLPT-N1&#xff08;1&a…

(一)使用 LangChain 从零开始构建 RAG 系统|RAG From Scratch

RAG 的主要动机 大模型训练的时候虽然使用了庞大的世界数据&#xff0c;但是并没有涵盖用户关心的所有数据&#xff0c; 其预训练令牌&#xff08;token&#xff09;数量虽大但相对这些数据仍有限。另外大模型输入的上下文窗口越来越大&#xff0c;从几千个token到几万个token,…

OpenCV学习探秘之一 :了解opencv技术及架构解析、数据结构与内存管理​等基础

​一、OpenCV概述与技术演进​ 1.1技术历史​ OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是由Intel于1999年发起创建的开源计算机视觉库&#xff0c;后来交由OpenCV开源社区维护&#xff0c;旨在为计算机视觉应用提供通用基础设施。经历20余年发展&…

什么是JUC

摘要 Java并发工具包JUC是JDK5.0引入的重要并发编程工具&#xff0c;提供了更高级、灵活的并发控制机制。JUC包含锁与同步器&#xff08;如ReentrantLock、Semaphore等&#xff09;、线程安全队列&#xff08;BlockingQueue&#xff09;、原子变量&#xff08;AtomicInteger等…

零基础学后端-PHP语言(第二期-PHP基础语法)(通过php内置服务器运行php文件)

经过上期的配置&#xff0c;我们已经有了php的开发环境&#xff0c;编辑器我们继续使用VScode&#xff0c;如果是新来的朋友可以看这期文章来配置VScode 零基础学前端-传统前端开发&#xff08;第一期-开发软件介绍与本系列目标&#xff09;&#xff08;VScode安装教程&#x…

扩散模型逆向过程详解:如何从噪声中恢复数据?

在扩散模型中&#xff0c;逆向过程的目标是从噪声数据逐步恢复出原始数据。本文将详细解析逆向条件分布 q(zt−1∣zt,x)q(\mathbf{z}_{t-1} \mid \mathbf{z}_t, \mathbf{x})q(zt−1​∣zt​,x)的推导过程&#xff0c;揭示扩散模型如何通过高斯分布实现数据重建。1. 核心问题 在…

2025年7月份实时最新获取地图边界数据方法,省市区县街道多级联动【文末附实时geoJson数据下载】

动态生成最新行政区划 GeoJSON 数据并结合 ECharts 实现地图下钻功能 在开发基于地图的数据可视化应用时&#xff0c;一个常见的挑战是获取准确且最新的行政区划边界数据&#xff08;GeoJSON&#xff09;。许多现有的在线资源可能数据陈旧&#xff0c;无法反映最新的行政区划调…

Spark实现WorldCount执行流程图

spark可以分区并行执行&#xff0c;同时并行执行也可以基于内存完成迭代代码对于大部分spark程序来说都是以driver开始driver结束&#xff0c;中间都是executor分布式运行

编程与数学 03-002 计算机网络 02_网络体系结构与协议

编程与数学 03-002 计算机网络 02_网络体系结构与协议一、网络体系结构的基本概念&#xff08;一&#xff09;分层体系结构的优点&#xff08;二&#xff09;协议、接口与服务的概念二、OSI参考模型&#xff08;一&#xff09;七层模型的层次划分及功能&#xff08;二&#xff…

Flutter 提取图像主色调 ColorScheme.fromImageProvider

从图像中提取主色调&#xff0c;用于动态适配颜色主题或者界面颜色。之前在 Flutter 应用里一直用的 palette_generator 插件&#xff0c;可以分析图像颜色&#xff0c;从中提取一系列主要的色调。最近发现这个谷歌官方的插件竟然不维护了&#xff0c;后续没有更新计划了。 查找…

51c自动驾驶~合集8

自己的原文哦~ https://blog.51cto.com/whaosoft/11618683 #Hierarchical BEV BEV进入定制化时代&#xff01;清华Hierarchical BEV&#xff1a;创新多模块学习框架&#xff0c;无痛落地无缝量产&#xff01;​ 论文思路 自动驾驶指通过传感器计算设备、信息通信、自…

Excel——重复值处理

识别重复行的三种方法方法1&#xff1a;COUNTIF公式法在E2单元格输入公式&#xff1a;COUNTIF($B$2:$B2,B2)>1下拉填充至所有数据行结果为TRUE的即为重复行&#xff08;会标出第二次及以后出现的重复项&#xff09;方法2&#xff1a;排序IF公式法按商机号排序&#xff08;数…

华普微Matter模块HM-MT7201,打破智能家居生态孤岛

随着智能家居渗透率与认可度的持续提升&#xff0c;消费者对于智能家居的功能诉求正从具备联网控制、远程控制与语音遥控等基础交互能力&#xff0c;升级为能通过单一的家居生态平台APP无缝控制所有的品牌设备&#xff0c;从而实现真正意义上的统一调度。这种从“单一设备联网控…

如何使用 minio 完成OceanBase社区版的归档和备份

自OceanBase社区版4.2.1BP7版本起&#xff0c;OceanBase的归档与备份功能开始兼容AWS S3及S3协议的对象存储服务&#xff0c;因此&#xff0c;许多用户选择采用 MinIO 作为其备份存储介质。因为 MinIO 兼容AWS S3云存储服务接口&#xff0c;成为了一个轻便的服务选项。 本文将…

Nacos-服务注册,服务发现(二)

Nacos健康检查 两种健康检查机制 Nacos作为注册中⼼, 需要感知服务的健康状态, 才能为服务调⽤⽅提供良好的服务。 Nacos 中提供了两种健康检查机制&#xff1a; 客⼾端主动上报机制&#xff1a; 客⼾端通过⼼跳上报⽅式告知服务端(nacos注册中⼼)健康状态, 默认⼼跳间隔5…

手写PPO_clip(FrozenLake环境)

参考&#xff1a;白话PPO训练 成功截图 算法组件 四大部分 同A2C相比&#xff0c;PPO算法额外引入了一个old_actor_model. 在PPO的训练中&#xff0c;首先使用old_actor_model与环境进行交互得到经验&#xff0c;然后利用一批经验优化actor_model&#xff0c;最后再将actor_m…