目录
日志功能开发
常见的日志等级
日志功能代码
编译功能开发
创建子进程和程序替换
重定向
编译功能代码
日志功能开发
日志在软件开发和运维中起着至关重要的作用,目前我们不谈运维只谈软件开发;日志最大的作用就是用于故障排查和调试;
当系统出现问题时,日志记录可以帮助开发人员追踪问题的根源。通过查看日志文件,开发人员可以了解系统在发生故障之前的行为,识别错误发生的时间、地点和原因,并快速定位到错误的代码或功能模块。
因此我们要为目前这个项目编写一个简单的日志模块,这个模块可能被项目中的任何一个部分使用,我们将其放在公共模块的一个文件中。
常见的日志等级
DEBUG(调试): 用于记录程序的详细运行信息,通常用于开发和调试阶段,帮助开发人员定位问题和追踪程序流程。
INFO(信息): 用于记录程序正常运行时的重要信息,例如启动信息、关键操作记录等,可用于了解系统的基本运行情况。
WARNING(警告): 用于记录一些潜在的问题或异常情况,虽然不会导致系统崩溃或功能失效,但需要开发人员注意和处理,以避免可能的错误。
ERROR(错误): 用于记录错误事件,表示程序发生了一些可恢复的错误,但并未导致程序完全失败,通常需要开发人员及时处理以保证系统的正常运行。
FATAL(致命错误): 用于记录严重错误事件,表示程序发生了无法恢复的错误,导致程序崩溃或功能失效,需要立即进行修复和处理,以保证系统的稳定性和可靠性。
基于这个项目,目前我们实现的时文件版本;因此我们需要打印出日志等级、文件名称、报错行、时间戳。
日志功能代码
Log.hpp
#pragma once
#include <iostream>
#include <string>
#include "util.hpp"
using namespace std;
namespace ns_log
{
using namespace ns_util;
// 日志等级
enum
{
INFO, // 就是整数0
DEBUG, // 1
WARNING, // 2
ERROR, // 3
FATAL // 4
};
// 参数
// 日志等级
// 文件名
// 行数
// 标准输出流返回给用户
inline ostream &Log(const string &level, const string &file_name, const int line)
{
// 添加日志等级
string message = "[";
message += level;
message += "]";
// 添加报错文件名称
message += "[";
message += file_name;
message += "]";
// 添加报错行
message += "[";
message += to_string(line);
message += "]";
// 日志时间戳
message += "[";
message += TimeUtil::GetTimeStamp();
message += "]";
// cout 本质内部是包含缓冲区的
//
cout << message; // 不要endl刷新
// 此时message 就在缓冲区中
return cout;
}
// LOG(INFO)<<"message"<<endl;
// 开放日志
#define LOG(level) Log(#level, __FILE__, __LINE__)
}
最后我们宏定义这个函数调用,参数#level
用于将参数 level
转换成一个字符串,参数__FILE__
和 __LINE__
是预定义的宏,在编译时由编译器自动替换为当前源文件的文件名和代码行号。
时间信息也是一个公用的信息我们会在另一个文件中实现。
编译功能开发
首先我们要明白这个模块只负责进行代码编译,那么意味着我们目前默认是能够接收远端提交的代码文件,并且这个文件对于我们编译模块来说是一个临时文件,如果编译成功后也会形成一个临时的可执行文件,因此在这个模块中我们还需要一个临时文件的文件夹。但是对于这个模块中目前的进程是用来接收代码文件的,对于提交的代码文件我们如何处理呢?
创建子进程和程序替换
因为编译对于操作系统来也是执行了一个程序,只不过这个程序很小,过程快而已;因此我们可以创建一个子进程,让子进程进行程序替换,替换我们的编译指令程序。
重定向
编译代码就两种结果:编译成功或者编译失败;编译成功就是我们想要的结果,可以通过判断是否生成可执行文件判断这个结果;对于编译失败,编译失败的信息会向我们的显示器打印,但是我们是要将信息返回给使用者的;因此我们就需要将错误信息重定向到一个错误文件中。
编译功能代码
compiler.hpp
// 主要进行编译服务
#pragma once
// 编译服务器
#include <iostream>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<fcntl.h>
#include"../comm/util.hpp"
#include"../comm/Log.hpp"
using namespace std;
// 只负责进行代码编译
// 默认代码能够接收
// 远端提交代码
// 一定要能够形成临时文件
// 第一种编译通过
// 第二种编译出错
// 本质是像stderr错误中打印---->需要形成临时文件 ,帮助我们保存编译出错的结果的
// 不能让这个进程编译
// 核心思路:创建子进程(fork())---->子进程完成编译代码 编译错误向stderr中打印(默认是向显示屏打印) 需要重定向到stderr中
// 父进程继续执行
namespace ns_compiler
{
//引入路径拼接功能
using namespace ns_util;
using namespace ns_log;
class Compiler
{
public:
Compiler()
{
}
~Compiler()
{
}
//编译
//返回值:编译成功:true 编译失败:false
//参数:编译代码的文件名
//1234
//./temp/1234.cpp
//./tmep/1234.exe
//./temp/1234.stderr//编译错误临时文件
static bool Compile(const std::string &file_name)//temp文件夹保存临时文件
{
pid_t pid = fork();
if(pid<0)
{
//创建子进程失败
LOG(ERROR)<<"内部错误,创建子进程失败"<<"\n";
return false;
}
else if(pid==0)
{
//需要一个错误文件
int _stderr = open(PathUtil::Stderr(file_name).c_str(),O_CREAT|O_WRONLY,644);//
if(_stderr<0)
{
//打开文件失败
LOG(WARNING)<<"没有成功形成stderr文件"<<"\n";
exit(1);
}
//编译错误时才会向文件中写入
//重定向
dup2(_stderr,2);
//子进程:调用编译器完成对代码的编译工作
//程序替换
//不需要冗余的路径
//g++ -o target src -std=c++11
//程序替换并不影响文件描述符表
//我想执行谁,怎么执行
execlp("g++","g++","-o",PathUtil::Exe(file_name).c_str(),PathUtil::Src(file_name).c_str(),"-std=c++11",nullptr/*不要忘记*/);
LOG(ERROR) <<"启动编译器g++失败,可能是参数错误"<<"\n";
exit(2);
}
else
{
//父进程
waitpid(pid,nullptr,0);
//编译是否成功---->是否形成可执行程序
if(FileUtil::IsFileExists(PathUtil::Exe(file_name)))
{
LOG(INFO)<<PathUtil::Src(file_name)<<" 编译成功!"<<"\n";
return true;
}
}
LOG(ERROR)<<"编译失败,没有形成可执行程序"<<"\n";
return false;
}
};
}
until.hpp
#pragma once
#include <iostream>
#include <string>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/time.h>
using namespace std;
namespace ns_util
{
const std::string temp_path = "./temp/";
// 路径
class PathUtil
{
public:
static std::string AddSuffix(const std::string &file_name, const std::string suffix)
{
std::string path_name = temp_path;
path_name += file_name;
path_name += suffix;
return path_name;
}
// 构建源文件路径+后缀的完整文件名
// 1234->./temp/1234.cpp
static std::string Src(const std::string &file_name)
{
return AddSuffix(file_name, ".cpp");
}
// 构建可执行程序的完整路径+后缀
static std::string Exe(const std::string &file_name)
{
return AddSuffix(file_name, ".exe");
}
// 构建该程序对应的标准错误完整的的路径+后缀名
static std::string Stderr(const std::string &file_name)
{
return AddSuffix(file_name, ".stderr");
}
};
class FileUtil
{
public:
static bool IsFileExists(const std::string path_name)
{
// 通过判断获取文件属性判断文件是否存在
struct stat st;
if (stat(path_name.c_str(), &st) == 0)
{
// 获取文件属性成功代表文件存在
return true;
}
return false;
}
};
class TimeUtil
{
public:
static string GetTimeStamp()
{
struct timeval _time;
gettimeofday(&_time, nullptr);
return to_string(_time.tv_sec);
}
};
}
今天对项目编译和日志功能开发的分享到这就结束了,希望大家读完后有很大的收获,也可以在评论区点评文章中的内容和分享自己的看法;个人主页还有很多精彩的内容。您三连的支持就是我前进的动力,感谢大家的支持!!!