项目功能
主要功能:接收来自多个节点的日志,按节点名存入文件。
分析功能:设定多个Tag,将日志与Tag匹配。最终可以让用户指定Tag,将所有匹配日志存入指定文件。
对外接口类
LogService
主要对外接口。
启动后持续运行,接收节点日志,交给Handler类处理。
用户可以设定并行线程数。
TagManager
管理Tag。
单例。
Analyst
分析数据。
生成Tag文件。
内部实现类
FileWriter
打开文件写入。
Handler类
处理日志,包括存入文件、匹配Tag存入Cache。
Cache类
保存与Tag匹配成功的日志。
数据类型
来自节点的任务
struct LogTask {
std::string host; // 发送者
std::string msg; // 消息
};
存入Cache的缓存信息
struct LogInfo {
std::string file; // 文件名
size_t line; // 行号
};
共享队列,在多线程中保证队列安全
class SharedQueue;
工作流程
用户调用TagManager接口,生成Tag。
用户构造LogService,设定线程数。
LogService拥有成员Handler [ ],每个Handler用一个线程。
LogService拥有成员Cache,将Cache传给Handler [ ]。用户调用LogService接口添加节点。
LogService拥有成员FileWriter [ ],LogService为每一个节点生成FileWriter,
并将FileWriter [ ] 传给Handler [ ]。用户启动LogService。
用户调用LogService的接口添加任务,LogService直接把任务平均分配到Handler的任务队列,每个Handler拥有一个任务队列。
Handler用LogService传入的FileWriter,将日志写入节点文件,写入函数会返回行号。
Handler从TagManager获得Tags,做匹配,匹配成功则将<节点文件名:行号>存入Cache。直到用户调用LogService接口停止服务,停止服务后LogService接口无法再接收任务,所有Handler完成任务队列。
用户调用Analyst的接口,指定Tag,指定文件名。
Analyst类调用LogService接口获取Cache。调用Cache接口,从Cache中得到与Tag匹配的所有日志在文件中的储存信息。Analyst从文件中读取与Tag匹配的所有日志,生成到指定文件名的文件中。
流程图 (点击放大)
从本次开发中获得的经验
C++开发经验之谈1_zidian666的博客-CSDN博客
改正/优化
void TagManager::remove(const std::string &tag)
{
std::unique_lock<std::mutex> ulock(mtx_tags_);
tags_.erase(tag);
}
Tag被romove后,应从cache中删除相关缓存
void TagManager::remove(const std::string &tag)
{
cache_->remove(tag);
std::unique_lock<std::mutex> ulock(mtx_tags_);
tags_.erase(tag);
}
void TagManager::remove(const std::string &tag)
{
cache_->remove(tag);
std::unique_lock<std::mutex> ulock(mtx_tags_);
tags_.erase(tag);
}
在借用作用域释放锁的地方,用lock_guard而不是unique_lock。
用{}可以轻松限制作用域。
void TagManager::remove(const std::string &tag)
{
cache_->remove(tag);
std::lock_guard<std::mutex> ulock(mtx_tags_);
tags_.erase(tag);
}
class TagManager
{
public:
/**
* @brief 获取当前所有生效的条目
*/
std::map<std::string, bool> &get_all_tags();
{
return tags_;
}
private:
std::map<std::string, bool> tags_; // string: tag, bool: enabled
std::mutex mtx_tags_;
Cache *cache_ = nullptr;
};
返回类成员的引用可能会导致:
外部类读取类成员同时,类成员被类对象修改,未同步;
外部类修改类成员;
std::vector<std::string> TagManager::get_all_tags()
{
std::lock_guard<std::mutex> ulock(mtx_tags_);
std::vector<std::string> ret;
ret.reserve(tags_.size());
for (auto &each : tags_)
{
if (each.second)
ret.push_back(each.first);
}
return ret;
}
class LogServcie{
public:
void addHandler()
{
task_queues_.push_back(SharedQueue<Task>());
handlers.emplace_back(Handler(task_queues_.back()));
}
void addTask(Task task)
{
task_queues_[total_task_num_++ % task_queues.size()].push(task);
}
private:
size_t total_task_num_ = 0;
std::vector<Handler> handlers_;
std::vector<SharedQueue<Task>> task_queues_;
};
class Handler{
public:
Handler(SharedQueue &task_queue) : task_queue_(task_queue) {}
void handleTask();
private:
SharedQueue<Task> &task_queue_;
}
某些成员可以作为处理它的类的成员,而不是以传参的形式将成员传给处理它的类
此例中的task_queue。
class LogServcie{
public:
void addHandler()
{
handlers.emplace_back(Handler());
}
void addTask(Task task)
{
handlers_[total_taks_num_++ % handlers_.size()].addTask(task);
}
private:
size_t total_task_num_ = 0;
std::vector<Handler> handlers_;
};
class Handler{
public:
Handler() = default;
void addTask(Task task)
{
task_queue_.push(task);
}
void handleTask();
private:
SharedQueue<Task> task_queue_;
}
class SharedQueue{
void push(Task);
void pop();
Task front();
//各操作保证同步
};
class Handler{
void handle()
{
Task = task_queue_.front();
task_queue_.pop();
}
private:
SharedQueue<Task> task_queue_;
};
即使类中操作保证同步,但多线程调用仍可能导致异步。
如果两个Handler各一个线程对同一个SharedQueue操作,本期望:
thread1.front(); thread1.pop(); thread2.front(); thread2.pop();
实际可能:
thread1.front(); thread2.front(); thread1.pop(); thread2.pop();
在SharedQueue中增加功能frontAndPop()可解决这个问题。
template <typename T>
class SharedQueue
{
public:
T SharedQueue<T>::frontAndPop();
private:
std::deque<T> queue_;
std::mutex mutex_;
std::condition_variable cond_;
};
template <typename T>
T SharedQueue<T>::frontAndPop()
{
std::unique_lock<std::mutex> mlock(mutex_);
while (queue_.empty())
{
cond_.wait(mlock);
}
T temp = queue_.front();
queue_.pop_front();
return temp;
}
class Handler{
public:
Handler() = default;
void addTask(Task *task)
{
task_queue_.push(*task);
delete task;
}
void handleTask();
private:
SharedQueue<Task> task_queue_;
}
某些时候,存指针所指值,不如直接存指针。
class Handler{
public:
Handler() = default;
void addTask(Task *task)
{
task_queue_.push(task);
delete task;
}
void handleTask()
{
Task *tmp = task_queue_.frontAndPop();
do something with tmp;
delete tmp;
}
private:
SharedQueue<Task*> task_queue_;
}
std::transform(src.begin(),src.end(),dist.begin(),(int (*)(int))std::tolower);
官方文档中不建议这样使用std::toupper或std::tolower;
由此得到的收获是:第一次使用的东西去查官方文档。
std::transform(lower_tag.begin(), lower_tag.end(), lower_tag.begin(),
[](unsigned char c)
{
return std::tolower(c);
});
void LogService::addTask(Task *task)
{
if (!stop_flag && !task)
{
handlers_[total_task_num % handlers_.size()].addTask(task);
++total_task_num;
}
}
两个条件检测,或许可以把更重要的放在前面。
void LogService::addTask(LogTask *task)
{
if (!task)
return;
if (!stop_flag)
{
handlers_[total_task_num % handlers_.size()].addTask(task);
++total_task_num;
}
}
std::map<std::string, bool> tags;
std::string tag;
if (tags.find(tag) != tags.end())
{
if (tags[tag].second)
do something with tags[tag].first;
}
多次用到查找,不如用一个迭代器解决。
std::map<std::string, bool> tags;
std::string tag;
auto where = tags.find(tag);
if (where != tags.end())
{
if (where->second)
do something with where.first;
}
FILE* file_ = fopen(file.c_str(), "w+");
std::string content;
fputs(content.c_str(), file_);
fputs函数会将原本写入文件的内容放在缓冲区,直到调用fclose()或fflush()才能将缓冲区内容写入文件。
通过setvbuf()可以设置缓冲模式。
建议使用更广泛使用的fread()和fwrite()。
FILE* file_ = fopen(file.c_str(), "w+");
std::string content;
fputs(content.c_str(), file_);
// _IOLBF, 行缓冲模式, 遇到'\n'或缓冲区满时写入文件
setvbuf(file_, NULL, _IOLBF, 1024);