精华内容
下载资源
问答
  • 怎么设计系统的登录日志
    万次阅读
    2018-08-15 15:44:09
    原文地址为: 浅谈管理系统操作日志设计(附操作日志类)

      管理系统的操作日志如何做成通用的模块一直是个让我头疼的问题,不过看了博客园里的某篇文章后,现在基本解决了。

      相关文章链接:《系统操作日志设计

      在开始做之前,必须把两个日志分清楚,那就是普通操作日志业务操作日志,这两者有何区别?

      在我理解,普通操作日志就是单表的操作记录,而业务操作日志则就是一系列的普通操作日志的集合。

      打个比方,用户需要购买一样宝贝,已经到了下单那步,下单就是个业务,这个业务背后就是一系列的业务,如:

      生成订单 → 生成商品快照 → 发送一条站内信 → 删除购物车里对应宝贝

      这样一个下单操作就包含了4部分,可以把这4部分看成是4张表,分别对这4张表进行对应的操作,就实现了业务。

      但今天我要讲的不是业务操作日志,因为不同项目的业务不尽相同,所以它无法做成通用模块,而我要讲的,就是普通操作日志。

      上面解释了一大段,下面干货就要亮相了,先洗把脸清醒下。

      ……

      首先,哪些地方需要记录操作日志?执行insert、update、delete这3个操作的时候,就需要进行日志,而日志执行的先后顺序如下

    insert在insert后执行
    update在update前后都要执行,操作前获取操作前数据,操作后获取操作后数据
    delete在delete前执行

      顺序清楚后,就来看下我写的一份日志操作类吧,第一版随便写写的,重复代码有点多,还未来得及优化。

    class LOG{
    protected $primaryid;
    protected $tbid;
    protected $tbname;
    protected $keys;
    protected $values;
    /**
    * 参数说明
    * int $tbid 查询指定表的id
    * string $tbname 数据库表名
    */
    public function insert($tbid, $tbname){
    global $db;
    //查询表注释
    $db->query('show table status where name = "'.$tbname.'"');
    $tb = $db->fetch();
    //插入日志主表
    $returnid = $db->insert(0, 2, 'tb_log', array(
    'adminid = '.$_SESSION['admin']['id'],
    'type = 1',
    'tableid = '.$tbid,
    'tablename = "'.$tbname.'"',
    'comment = "'.$tb['Comment'].'"',
    'dt = now()'
    ));
    //查询字段注释
    $db->query('show full columns from '.$tbname);
    $tb = $db->fetchAll();
    foreach($tb as $v){
    $commentArray[$v['Field']] = $v['Comment'];
    }
    //查询所有字段信息,插入日志从表
    $rs = $db->select(0, 1, $tbname, '*', 'and tbid = '.$tbid);
    $keys = array_keys($rs);
    $values = array_values($rs);
    for($i = 0; $i < count($keys); $i++){
    $db->insert(0, 0, 'tb_log_content', array(
    'logid = '.$returnid,
    'tbkey = "'.$keys[$i].'"',
    'tbvalue = "'.$values[$i].'"',
    'comment = "'.$commentArray[$keys[$i]].'"'
    ));
    }
    }
    public function updateStart($tbid, $tbname){
    global $db;
    //查询表注释
    $db->query('show table status where name = "'.$tbname.'"');
    $tb = $db->fetch();
    //插入日志主表
    $returnid = $db->insert(0, 2, 'tb_log', array(
    'adminid = '.$_SESSION['admin']['id'],
    'type = 2',
    'tableid = '.$tbid,
    'tablename = "'.$tbname.'"',
    'comment = "'.$tb['Comment'].'"',
    'dt = now()'
    ));
    //查询修改前数据信息
    $rs = $db->select(0, 1, $tbname, '*', 'and tbid = '.$tbid);
    $keys = array_keys($rs);
    $values = array_values($rs);
    $this->primaryid = $returnid;
    $this->tbid = $tbid;
    $this->tbname = $tbname;
    $this->keys = $keys;
    $this->values = $values;
    }
    public function updateEnd(){
    global $db;
    //查询字段注释
    $db->query('show full columns from '.$this->tbname);
    $tb = $db->fetchAll();
    foreach($tb as $v){
    $commentArray[$v['Field']] = $v['Comment'];
    }
    //查询修改后数据信息
    $rs = $db->select(0, 1, $this->tbname, '*', 'and tbid = '.$this->tbid);
    $currentvalues = array_values($rs);
    //前后信息进行比较
    for($i = 0; $i < count($currentvalues); $i++){
    if($this->values[$i] !== $currentvalues[$i]){
    $db->insert(0, 0, 'tb_log_content', array(
    'logid = '.$this->primaryid,
    'tbkey = "'.$this->keys[$i].'"',
    'tbvalue = "'.$this->values[$i].'"',
    'currenttbvalue = "'.$currentvalues[$i].'"',
    'comment = "'.$commentArray[$this->keys[$i]].'"'
    ));
    }
    }
    }
    public function delete($tbid, $tbname){
    global $db;
    //查询表注释
    $db->query('show table status where name = "'.$tbname.'"');
    $tb = $db->fetch();
    //插入日志主表
    $returnid = $db->insert(0, 2, 'tb_log', array(
    'adminid = '.$_SESSION['admin']['id'],
    'type = 3',
    'tableid = '.$tbid,
    'tablename = "'.$tbname.'"',
    'comment = "'.$tb['Comment'].'"',
    'dt = now()'
    ));
    //查询字段注释
    $db->query('show full columns from '.$tbname);
    $tb = $db->fetchAll();
    foreach($tb as $v){
    $commentArray[$v['Field']] = $v['Comment'];
    }
    //查询所有字段信息,插入日志从表
    $rs = $db->select(0, 1, $tbname, '*', 'and tbid = '.$tbid);
    $keys = array_keys($rs);
    $values = array_values($rs);
    for($i = 0; $i < count($keys); $i++){
    $db->insert(0, 0, 'tb_log_content', array(
    'logid = '.$returnid,
    'tbkey = "'.$keys[$i].'"',
    'tbvalue = "'.$values[$i].'"',
    'comment = "'.$commentArray[$keys[$i]].'"'
    ));
    }
    }
    }

      使用前,需要引入数据库操作类,这是我之前写的一份,可参考《全新的数据库操作类(仅适用)》。

      引入之后,就可以开始使用了。

      select

    $log->insert(82, 'tb_member');

      update

    $log->updateStart(82, 'tb_member');
    //中间放更新操作代码
    $log->updateEnd();

      delete

    $log->delete(82, 'tb_member');

      可以看到,一共只需要两个参数即可,分别是表ID(主键)和表名称。

      另外需要强调一点,表注释和字段注释一定要完整,因为记录的信息包含注释,目的就是为了查阅的时候能清楚哪个字段是干什么用的。

      下面就看下成品吧

      最后把表结构分享下,一共2张表,一张主表一张从表,主表记录操作表及操作人等信息,从表记录操作的表字段信息。

    -- ----------------------------
    -- Table structure for `tb_log`
    -- ----------------------------
    CREATE TABLE `tb_log` (
    `tbid` bigint(20) NOT NULL AUTO_INCREMENT,
    `adminid` bigint(20) DEFAULT NULL COMMENT '管理员id',
    `type` tinyint(4) DEFAULT '1' COMMENT '操作类型:1新增2修改3删除',
    `tableid` bigint(20) DEFAULT NULL,
    `tablename` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '表名',
    `comment` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
    `dt` datetime DEFAULT NULL,
    PRIMARY KEY (`tbid`)
    ) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

    -- ----------------------------
    -- Table structure for `tb_log_content`
    -- ----------------------------
    CREATE TABLE `tb_log_content` (
    `tbid` bigint(20) NOT NULL AUTO_INCREMENT,
    `logid` bigint(20) DEFAULT NULL,
    `tbkey` longtext COLLATE utf8_unicode_ci,
    `tbvalue` longtext COLLATE utf8_unicode_ci,
    `currenttbvalue` longtext COLLATE utf8_unicode_ci,
    `comment` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
    PRIMARY KEY (`tbid`)
    ) ENGINE=InnoDB AUTO_INCREMENT=109 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

    转载请注明本文地址: 浅谈管理系统操作日志设计(附操作日志类)
    更多相关内容
  • 真正通用的操作日志系统设计

    热门讨论 2011-08-12 09:31:42
    真正通用的操作日志系统设计,这个是以前看到的资料,觉得不错。此文档版权属于作者本人“加伊”。
  • C++设计实现日志系统

    千次阅读 多人点赞 2019-11-16 20:32:18
    日志系统几乎是每一个实际的软件项目从开发、测试到交付,再到后期的维护...本文Jungle将用C++设计实现一个日志系统。 1.为什么需要日志 为什么需要日志?其实在引言中已经提到了,实际的软件项目的几乎每个过...

    日志系统几乎是每一个实际的软件项目从开发、测试到交付,再到后期的维护过程中极为重要的查看软件代码运行流程还原错误现场记录运行错误位置及上下文等的重要依据。一个高性能的日志系统,能够准确记录重要的变量信息,同时又没有冗余的打印导致日志文件记录无效的数据。本文Jungle将用C++设计实现一个日志系统。

    1.为什么需要日志

    为什么需要日志?其实在引言中已经提到了,实际的软件项目的几乎每个过程,都离不开日志。初学代码时,Jungle的第一行代码是实现打印“hello world”,打印到控制台。在后来的学习中,Jungle又学会了设断点调试代码,在适当的地方通过断点来观察变量的值。但在实际的软件项目中,试想一下,通过输出到控制台或者通过设断点来调试代码,可能吗?

    • 客户现场,会让你现场打印到控制台上调试吗?
    • 报了error的软件项目,你能够明确知道软件crash的位置吗?
    • 你能保证设断点可以还原error时候的现场吗?
    • 概率性的error事件,设断点还奏效吗?
    • 如果是时效性的代码(比如USB连接) ,设断点调试还合理吗?
    • ……

    日志,可以记录每一时刻软件的运行情况,记录error或者crash时的信息(时间、关键变量的值、出错位置、线程等);另一方面,对于概率性error事件,可以在重复测试时通过日志来查询错误复现时候的情况。简言之,日志是跟踪和回忆某个时刻或者时间段内的程序行为进而定位问题的一种重要手段

    2.日志系统设计

    软件运行过程中,需要记录的有什么呢?前述已经提到,关键变量的值、运行的位置(哪个文件、哪个函数、哪一行)、时间、线程号、进程号。本文Jungle采用C++设计了LOG类,介绍LOG类的设计之前,需要提及的是log的级别和log位置。

    2.1.1.log级别

    Log级别是什么意思呢?在开发阶段,Jungle可能想尽可能详细地跟踪代码运行过程,所以可以打印尽可能多的信息到日志文件中;测试过程中,测试部可能不需要这么详细的信息,所以这时候有的信息可能不必输出到Log文件;产品交付客户使用时,为了软件运行更快、客户体验更好,这时候就只需打印关键信息到日志文件了,因为过多的写文件会耗费大量时间,影响软件运行速度。所以Jungle为LOG类定义了如下级别:

    enum LOGLEVEL
    {
    	LOG_LEVEL_NONE,
    	LOG_LEVEL_ERROR,     // error
    	LOG_LEVEL_WARNING,   // warning
    	LOG_LEVEL_DEBUG,     // debug
    	LOG_LEVEL_INFO,      // info	
    };

    在软件设计中,可以通过某些方法或者预留一些开关来设置Log级别,方便在开发、调试、测试和客户现场灵活地调整日志级别,以获取到有用的日志信息。 

    2.1.2.log输出位置

    Log文件可以输出到控制台(其实也是不错的方法),也可以输出到指定路径下的某个文件里,也可能有别的需求。比如,开发或调试时,简单的信息直接就打印到软件某个界面上;测试或者交付客户时,最好将日志保存到文件里,这样可以保存尽可能多的信息。因此,Jungle进行了如下设计:

    enum LOGTARGET
    {
    	LOG_TARGET_NONE      = 0x00,
    	LOG_TARGET_CONSOLE   = 0x01,
    	LOG_TARGET_FILE      = 0x10
    };

    2.1.3.log的作用域

    一个软件系统,要在哪儿输出日志呢?Everywhere!只要是你想打印日志的地方,任何一个函数、任何一个文件,都应该而且必须可以打印。也就是说这个log类的对象(不妨叫做日志记录器),日志记录器必须是全局的

    光是全局的就够了吗?你这个文件里有一个全局的日志记录器,输出日志到file.log文件里;另一个文件里也有一个日志记录器,也输出到file.log文件里……多个日志记录器同时往一个文件里写日志,这显然不合理。所以还必须保证日志记录器全局且唯一

    怎么保证日志记录器唯一呢?即Log类在具体的软件系统中有且仅有一个实例化对象。答案是采用单例模式!(设计模式(九)——单例模式

    2.2.日志类的设计

    综上所述,Jungle设计的日志类LOG如下:

    class LOG
    {
    public:
    
    	// 初始化
    	void init(LOGLEVEL loglevel, LOGTARGET logtarget);
    
    	// 
    	void uninit();
    
    	// file
    	int createFile();
    
    	static LOG* getInstance();
    
    	// Log级别
    	LOGLEVEL getLogLevel();
    	void setLogLevel(LOGLEVEL loglevel);
    
    	// Log输出位置
    	LOGTARGET getLogTarget();
    	void setLogTarget(LOGTARGET logtarget);
    
    	// 打log
    	static int writeLog(
    		LOGLEVEL loglevel,         // Log级别
    		unsigned char* fileName,   // 函数所在文件名
    		unsigned char* function,   // 函数名
    		int lineNumber,            // 行号
    		char* format,              // 格式化
    		...);                      // 变量
    
    	// 输出log
    	static void outputToTarget();
    
    private:
    	LOG();
    	~LOG();
    	static LOG* Log;
    
    	// 互斥锁
    	static mutex log_mutex;
    
    	// 存储log的buffer
    	static string logBuffer;
    
    	// Log级别
    	LOGLEVEL logLevel;
    
    	// Log输出位置
    	LOGTARGET logTarget;
    
    	// Handle
    	static HANDLE mFileHandle;
    };

    其中,互斥锁log_mutex是用于在多线程环境下保证只创建一个LOG类的实例 (设计模式(九)——单例模式);mFileHandle是log文件的句柄。

    2.3.日志类的实现

    2.3.1.初始化

    LOG*             LOG::Log         = NULL;
    string           LOG::logBuffer   = "";
    HANDLE           LOG::mFileHandle = INVALID_HANDLE_VALUE;
    mutex            LOG::log_mutex;
    
    LOG::LOG()
    {
    	// 初始化
    	init(LOG_LEVEL_NONE, LOG_TARGET_FILE);
    } 
    
    void LOG::init(LOGLEVEL loglevel, LOGTARGET logtarget)
    {
    	setLogLevel(loglevel);
    	setLogTarget(logtarget);
    	createFile();
    }
    
    void LOG::uninit()
    {
    	if (INVALID_HANDLE_VALUE != mFileHandle)
    	{
    		CloseHandle(mFileHandle);
    	}
    }
    
    LOG* LOG::getInstance()
    {
    	if (NULL == Log)
    	{
    		log_mutex.lock();
    		if (NULL == Log)
    		{
    			Log = new LOG();
    		}
    		log_mutex.unlock();
    	}
    	return Log;
    }
    
    LOGLEVEL LOG::getLogLevel()
    {
    	return this->logLevel;
    }
    
    void LOG::setLogLevel(LOGLEVEL iLogLevel)
    {
    	this->logLevel = iLogLevel;
    }
    
    LOGTARGET LOG::getLogTarget()
    {
    	return this->logTarget;
    }
    
    void LOG::setLogTarget(LOGTARGET iLogTarget)
    {
    	this->logTarget = iLogTarget;
    }

    初始化工作设置了日志的级别和输出位置(代码中提供了日志级别和输出位置的setter、getter方法)。函数createFile()是创建日志文件位置,并获取日志文件的句柄mFileHandle。代码如下:

    int LOG::createFile()
    {
    	TCHAR fileDirectory[256];
    	GetCurrentDirectory(256, fileDirectory);
    
    	// 创建log文件的路径
    	TCHAR logFileDirectory[256];
    	_stprintf_s(logFileDirectory, _T("%s\\Test\\"), fileDirectory);// 使用_stprintf_s需要包含头文件<TCHAR.H>
    
    	// 文件夹不存在则创建文件夹
    	if (_taccess(logFileDirectory, 0) == -1)
    	{
    		_tmkdir(logFileDirectory);
    	}
    
    	TCHAR cTmpPath[MAX_PATH] = { 0 };
    	TCHAR* lpPos = NULL;
    	TCHAR cTmp = _T('\0');
    
    	WCHAR pszLogFileName[256];
    	// wcscat:连接字符串
    	wcscat(logFileDirectory, _T("test.log"));
    	_stprintf_s(pszLogFileName, _T("%s"), logFileDirectory);
    	mFileHandle = CreateFile(
    		pszLogFileName,
    		GENERIC_READ | GENERIC_WRITE,
    		FILE_SHARE_READ,
    		NULL,
    		OPEN_ALWAYS,
    		FILE_ATTRIBUTE_NORMAL,
    		NULL);
    	if (INVALID_HANDLE_VALUE == mFileHandle)
    	{
    		return -1;
    	}
    	return 0;
    }

    其中,需要介绍的是下述函数:

    • GetCurrentDirectory:在一个缓冲区中装载当前目录
    • _stprintf_s:将若干个参数按照format格式存到buffer中
    • _taccess:判断文件是否存在,返回值0表示该文件存在,返回-1表示文件不存在或者该模式下没有访问权限
    • _tmkdir:创建一个目录

    2.3.2.写日志

    以下是writeLog()方法的实现:

    int LOG::writeLog(
    	LOGLEVEL loglevel,         // Log级别
    	unsigned char* fileName,   // 函数所在文件名
    	unsigned char* function,   // 函数名
    	int lineNumber,            // 行号
    	char* format,              // 格式化
    	...)
    {
    	int ret = 0;
    
    	// 获取日期和时间
    	char timeBuffer[100];
    	ret = getSystemTime(timeBuffer);
    	logBuffer += string(timeBuffer);
    
    	// LOG级别
    	char* logLevel;
    	if (loglevel == LOG_LEVEL_DEBUG){
    		logLevel = "DEBUG";
    	}
    	else if (loglevel == LOG_LEVEL_INFO){
    		logLevel = "INFO";
    	}
    	else if (loglevel == LOG_LEVEL_WARNING){
    		logLevel = "WARNING";
    	}
    	else if (loglevel == LOG_LEVEL_ERROR){
    		logLevel = "ERROR";
    	}
    
    	// [进程号][线程号][Log级别][文件名][函数名:行号]
    	char locInfo[100];
    	char* format2 = "[PID:%4d][TID:%4d][%s][%-s][%s:%4d]";
    	ret = printfToBuffer(locInfo, 100, format2,
    		GetCurrentProcessId(),
    		GetCurrentThreadId(),
    		logLevel,
    		fileName,
    		function,
    		lineNumber);
    	logBuffer += string(locInfo);	
    
    	// 日志正文
    	char logInfo2[256];
    	va_list ap;
    	va_start(ap, format);
    	ret = vsnprintf(logInfo2, 256, format, ap);
    	va_end(ap);
    
    	logBuffer += string(logInfo2);
    	logBuffer += string("\n");
    
    	outputToTarget();
    
    	return 0;
    }

    2.3.3.输出日志

    void LOG::outputToTarget()
    {
    	if (LOG::getInstance()->getLogTarget() & LOG_TARGET_FILE)
    	{
    		SetFilePointer(mFileHandle, 0, NULL, FILE_END);
    		DWORD dwBytesWritten = 0;
    		WriteFile(mFileHandle, logBuffer.c_str(), logBuffer.length(), &dwBytesWritten, NULL);
    		FlushFileBuffers(mFileHandle);
    	}
    	if (LOG::getInstance()->getLogTarget() & LOG_TARGET_CONSOLE)
    	{
    		printf("%s", logBuffer.c_str());
    	}
    
    	// 清除buffer
    	logBuffer.clear();
    }
    • SetFilePointer:将文件指针移动到文件指定的位置
    • FlushFileBuffers:把写文件缓冲区的数据强制写入磁盘

    为了使用方便,可以定义一些宏来简化函数的使用,本文不再赘述。

    3.测试

    Jungle将上述设计实现的日志系统应用到了之前写的一些小程序里,比如在之前的“欲戴王冠,必承其重”——深度解析职责链模式的代码。如何添加呢?就是将两个文件(头文件和源文件)加入工程,包含头文件,再在需要打log的地方加上Jungle在日志类里定义的宏即可。下列是示例log:

    因为程序比较简单,代码量很小,所以只有一个线程(log中TID都是一样的)。但上述测试结果验证了Jungle设计的日志系统是可行的。

    4.多线程环境

    4.1.多线程环境测试

    接下来Jungle设计一个简单的多线程环境,测试一下上述日志系统,测试代码如下:

    #define THREAD_NUM 5
    // 全局资源变量
    int g_num = 0;
    
    unsigned int __stdcall func(void *pPM)
    {
    	LOG_INFO("enter");
    	Sleep(50);
    	g_num++;
    	LOG_INFO("g_num = %d", g_num);
    	LOG_INFO("exit");
    	return 0;
    }
    
    int main()
    {
    	LOG *logger = LOG::getInstance();
    	HANDLE  handle[THREAD_NUM];
    
    	//线程编号
    	int threadNum = 0;
    	while (threadNum < THREAD_NUM)
    	{
    		handle[threadNum] = (HANDLE)_beginthreadex(NULL, 0, func, NULL, 0, NULL);
    		//等子线程接收到参数时主线程可能改变了这个i的值
    		threadNum++;
    	}
    	//保证子线程已全部运行结束
    	WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
    	return 0;
    }

    上述代码中,Jungle一共开启了5个线程,理论上打印的日志文件里,TID应该出现5个不同的数值。每个线程里打印全局变量(即全局共享资源)的值。下面是输出的日志,一共运行了两次(第5、6行隔开):

    问题来啦!

    首先,在第一次运行输出的日志里,出现了乱码!(第1行和第4行),而且看起来该输出log的地方没有完全输出(真的吗?)

    其次,在第二次运行输出的日志里,一行log里好像打印了两次日志(第8行)!

    问题出在哪里呢?

    为什么会出现乱码?仔细看第8行log,其实打印的都是同一个时刻、同一个位置,都是在调用writeLog函数(宏LOG_INFO即是调用writeLog函数)时出现的问题,也就是说在这个时刻,两个线程都跑到函数writeLog里写log,导致logBuffer缓冲区里存放了两次信息。只不过第8行运气较好,每次的编码都保存完整。而第1行和第4行就没这么走运了!(logBuffer里已经完全乱了!)所以根本问题是,多个线程在同一个时刻访问了同一个资源!所以针对多线程环境,我们需要做到共享资源的互斥

    4.2.线程安全的日志系统

    在单例模式的设计实现里已经提到了线程安全,Jungle用互斥锁达到了互斥的目的。本文也可以使用互斥锁(并且在日志对象实例的单例模式中已经使用),但在这里Jungle想用另一种方法:临界区。

    在Log类成员里声明一个CRITICAL_SECTION对象criticalSection,初始化时:

    InitializeCriticalSection(&criticalSection);

    当然,最好在释放资源时加上下述代码:

    DeleteCriticalSection(&criticalSection);

    而在进入writeLog时和离开writeLog时加上下述代码:

    int LOG::writeLog(...)
    {
    	int ret = 0;
    
    	EnterCriticalSection(&criticalSection);
        
            // do something
    
    	LeaveCriticalSection(&criticalSection);
    
    	return 0;
    }
    

    需要提及的是,最好是在LeaveCriticalSection之后再DeleteCriticalSection。

    接下来再在多线程环境里测试,Jungle测试了几次,但为了缩短篇幅,只展示一次的结果:

    可以看到,日志完整记录了每个线程的运行过程(线程号TID不同)。

    5.注意事项

    尽管上述已经基本实现了日志系统,但仍有很大的改进空间,在调试代码和查阅资料的过程中,Jungle发现需要注意以下几个问题:

    1. 字符编码问题:宽字符、ANSI编码等多种不同编码的兼容;
    2. Visio Studio版本的差异:Jungle本想将日志系统应用到之前设计的一个机器人仿真控制器里,但遗憾的是编译不通过,因为那个代码是用Visio Studio 2008写的,而Mutex是C++2011标准的内容,需要用支持该新标准的编译器,比如VS2012及以上版本。(当然了,可以用临界区等其他方法实现互斥,这里Jungle只是提出这个需要注意的问题);
    3. 关于宏_CRT_SECURE_NO_WARNINGS:是的,需要在预处理器里加上这个宏或者代码里显示声明这个宏,否则编译不通过,如下图。原因是代码中使用的wcscat等函数不安全,可能会造成内存泄露等。解决方法除了前述提到的声明宏以外,还可以使用更安全的函数。

     最后,推荐两篇不错的关于日志系统的文章:

    欢迎评论区与Jungle交流,欢迎关注Jungle的公众号!


    欢迎关注知乎专栏:Jungle是一个用Qt的工业Robot

    欢迎关注Jungle的微信公众号:Jungle笔记

     

    展开全文
  • 系统操作日志设计

    万次阅读 2015-12-06 15:02:57
    我们在做企业管理系统时,有多多少少都有对数据的完整性有所要求,比如要求系统不能物理删除记录,要求添加每一条数据时都要有系统记录、或者更新某条数据都需要跟踪到变化的内容、或者删除数据时需要记录谁删除了,...

    为什么要做操作日志?

    其实上文也描述了一些,其主要目的就是跟踪到每一个用户在系统的操作行为,如对数据进行查询、新增、编辑或删除甚至是登录等行为。更进一步的理解可以说是对用户使用系统情况的跟踪,对数据的跟踪防止数据意外删除、更改时有所记录,有所依据,以便对数据的还原,从某种程序上可以保护数据的完整性。

     

    系统设计

    场景

    我们现在有一张表叫Employee:

    ID int
    Name nvarchar(50)
    Gender nvarchar(2)
    DateCreated datetime
    CreateUser nvarchar(50)

    在aspx页面中可能会有EmployeeEdit.aspx(用来添加或更新Employee信息等操作),EmployeeList.aspx(用来查询或进行删除Employee信息等操作)

    好了,现在我们要对Empoyee表操作的信息做一个系统日志,那怎么办?

    也许你可以建立多一个表跟Employee表一模一样的,叫做EmployeeLog

    ID int
    Name nvarchar(50)
    Gender nvarchar(2)
    DateCreated datetime
    CreateUser nvarchar(50)
    LogCreated datetime
    OperationType int

    其中加多了一些附属的信息如LogCreated(日志添加日期)和OperationType(查询、新增、删除、更新)

    此时这种情况可能大家在做用户登录日志的时候是一件很常见的事件。

    但……问题来了,假如我需要对表EmployeeIncome(员工的收入情况)做日志那怎么办?

    好建立多一张表叫EmployeeIncomeLog来记录员工收入情况的操作日志。

    假如又需要对表FixedAsset(固定资产)进行日志记录那又怎么办?

     

    好了,大家可能意识到我们这样做不但会造成表数量的增倍,而且大大的增加了工作量和开发时间,对数据库表不易管理等情况。

     

    因此我们需要一个能够通过简单的配置和编写就可以完成以上功能的日志管理

    数据库设计

    系统日志设计

    包括三个表,

    LogSetting(日志设置)——用来存储配置业务表名、业务名称、主键等

    LogSettingDetail(日志设置明细)——用来存储配置业务表需要记录的详细内容,如Employee表中,我们可能需要记录字段Name、Gender等信息。

    LogOperation(操作日志)——用来记录用户对各种业务操作的内容情况。

     

    下篇将讨论用代码如何实现日志管理的功能,下面先来几张图:

    日志列表:

    日志管理列表

    查看日志内容:

    查看日志内容



    管理系统的操作日志:

    管理系统的操作日志如何做成通用的模块一直是个让我头疼的问题,不过看了博客园里的某篇文章后,现在基本解决了。

      相关文章链接:《系统操作日志设计

      在开始做之前,必须把两个日志分清楚,那就是普通操作日志业务操作日志,这两者有何区别?

      在我理解,普通操作日志就是单表的操作记录,而业务操作日志则就是一系列的普通操作日志的集合。

      打个比方,用户需要购买一样宝贝,已经到了下单那步,下单就是个业务,这个业务背后就是一系列的业务,如:

      生成订单 → 生成商品快照 → 发送一条站内信 → 删除购物车里对应宝贝

      这样一个下单操作就包含了4部分,可以把这4部分看成是4张表,分别对这4张表进行对应的操作,就实现了业务。

      但今天我要讲的不是业务操作日志,因为不同项目的业务不尽相同,所以它无法做成通用模块,而我要讲的,就是普通操作日志。

      上面解释了一大段,下面干货就要亮相了,先洗把脸清醒下。

      ……

      首先,哪些地方需要记录操作日志?执行insert、update、delete这3个操作的时候,就需要进行日志,而日志执行的先后顺序如下

    insert在insert后执行
    update在update前后都要执行,操作前获取操作前数据,操作后获取操作后数据
    delete在delete前执行

      顺序清楚后,就来看下我写的一份日志操作类吧,第一版随便写写的,重复代码有点多,还未来得及优化。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    class  LOG{
         protected  $primaryid ;
         protected  $tbid ;
         protected  $tbname ;
         protected  $keys ;
         protected  $values ;
         /**
          * 参数说明
          * int              $tbid       查询指定表的id
          * string           $tbname     数据库表名
          */
         public  function  insert( $tbid $tbname ){
             global  $db ;
             //查询表注释
             $db ->query( 'show table status where name = "' . $tbname . '"' );
             $tb  $db ->fetch();
             //插入日志主表
             $returnid  $db ->insert(0, 2,  'tb_log' array (
                 'adminid = ' . $_SESSION [ 'admin' ][ 'id' ],
                 'type = 1' ,
                 'tableid = ' . $tbid ,
                 'tablename = "' . $tbname . '"' ,
                 'comment = "' . $tb [ 'Comment' ]. '"' ,
                 'dt = now()'
             ));
             //查询字段注释
             $db ->query( 'show full columns from ' . $tbname );
             $tb  $db ->fetchAll();
             foreach ( $tb  as  $v ){
                 $commentArray [ $v [ 'Field' ]] =  $v [ 'Comment' ];
             }
             //查询所有字段信息,插入日志从表
             $rs  $db ->select(0, 1,  $tbname '*' 'and tbid = ' . $tbid );
             $keys  array_keys ( $rs );
             $values  array_values ( $rs );
             for ( $i  = 0;  $i  count ( $keys );  $i ++){
                 $db ->insert(0, 0,  'tb_log_content' array (
                     'logid = ' . $returnid ,
                     'tbkey = "' . $keys [ $i ]. '"' ,
                     'tbvalue = "' . $values [ $i ]. '"' ,
                     'comment = "' . $commentArray [ $keys [ $i ]]. '"'
                 ));
             }
         }
         public  function  updateStart( $tbid $tbname ){
             global  $db ;
             //查询表注释
             $db ->query( 'show table status where name = "' . $tbname . '"' );
             $tb  $db ->fetch();
             //插入日志主表
             $returnid  $db ->insert(0, 2,  'tb_log' array (
                 'adminid = ' . $_SESSION [ 'admin' ][ 'id' ],
                 'type = 2' ,
                 'tableid = ' . $tbid ,
                 'tablename = "' . $tbname . '"' ,
                 'comment = "' . $tb [ 'Comment' ]. '"' ,
                 'dt = now()'
             ));
             //查询修改前数据信息
             $rs  $db ->select(0, 1,  $tbname '*' 'and tbid = ' . $tbid );
             $keys  array_keys ( $rs );
             $values  array_values ( $rs );
             $this ->primaryid =  $returnid ;
             $this ->tbid =  $tbid ;
             $this ->tbname =  $tbname ;
             $this ->keys =  $keys ;
             $this ->values =  $values ;
         }
         public  function  updateEnd(){
             global  $db ;
             //查询字段注释
             $db ->query( 'show full columns from ' . $this ->tbname);
             $tb  $db ->fetchAll();
             foreach ( $tb  as  $v ){
                 $commentArray [ $v [ 'Field' ]] =  $v [ 'Comment' ];
             }
             //查询修改后数据信息
             $rs  $db ->select(0, 1,  $this ->tbname,  '*' 'and tbid = ' . $this ->tbid);
             $currentvalues  array_values ( $rs );
             //前后信息进行比较
             for ( $i  = 0;  $i  count ( $currentvalues );  $i ++){
                 if ( $this ->values[ $i ] !==  $currentvalues [ $i ]){
                     $db ->insert(0, 0,  'tb_log_content' array (
                         'logid = ' . $this ->primaryid,
                         'tbkey = "' . $this ->keys[ $i ]. '"' ,
                         'tbvalue = "' . $this ->values[ $i ]. '"' ,
                         'currenttbvalue = "' . $currentvalues [ $i ]. '"' ,
                         'comment = "' . $commentArray [ $this ->keys[ $i ]]. '"'
                     ));
                 }
             }
         }
         public  function  delete ( $tbid $tbname ){
             global  $db ;
             //查询表注释
             $db ->query( 'show table status where name = "' . $tbname . '"' );
             $tb  $db ->fetch();
             //插入日志主表
             $returnid  $db ->insert(0, 2,  'tb_log' array (
                 'adminid = ' . $_SESSION [ 'admin' ][ 'id' ],
                 'type = 3' ,
                 'tableid = ' . $tbid ,
                 'tablename = "' . $tbname . '"' ,
                 'comment = "' . $tb [ 'Comment' ]. '"' ,
                 'dt = now()'
             ));
             //查询字段注释
             $db ->query( 'show full columns from ' . $tbname );
             $tb  $db ->fetchAll();
             foreach ( $tb  as  $v ){
                 $commentArray [ $v [ 'Field' ]] =  $v [ 'Comment' ];
             }
             //查询所有字段信息,插入日志从表
             $rs  $db ->select(0, 1,  $tbname '*' 'and tbid = ' . $tbid );
             $keys  array_keys ( $rs );
             $values  array_values ( $rs );
             for ( $i  = 0;  $i  count ( $keys );  $i ++){
                 $db ->insert(0, 0,  'tb_log_content' array (
                     'logid = ' . $returnid ,
                     'tbkey = "' . $keys [ $i ]. '"' ,
                     'tbvalue = "' . $values [ $i ]. '"' ,
                     'comment = "' . $commentArray [ $keys [ $i ]]. '"'
                 ));
             }
         }
    }

      使用前,需要引入数据库操作类,这是我之前写的一份,可参考《全新的PDO数据库操作类(仅适用Mysql)》。

      引入之后,就可以开始使用了。

      select

    1
    $log ->insert(82,  'tb_member' );

      update

    1
    2
    3
    $log ->updateStart(82,  'tb_member' );
    //中间放更新操作代码
    $log ->updateEnd();

      delete

    1
    $log -> delete (82,  'tb_member' );

      可以看到,一共只需要两个参数即可,分别是表ID(主键)和表名称。

      另外需要强调一点,表注释和字段注释一定要完整,因为记录的信息包含注释,目的就是为了查阅的时候能清楚哪个字段是干什么用的。

      下面就看下成品吧

      最后把表结构分享下,一共2张表,一张主表一张从表,主表记录操作表及操作人等信息,从表记录操作的表字段信息。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    -- ----------------------------
    -- Table structure for `tb_log`
    -- ----------------------------
    CREATE  TABLE  `tb_log` (
       `tbid`  bigint (20)  NOT  NULL  AUTO_INCREMENT,
       `adminid`  bigint (20)  DEFAULT  NULL  COMMENT  '管理员id' ,
       `type` tinyint(4)  DEFAULT  '1'  COMMENT  '操作类型:1新增2修改3删除' ,
       `tableid`  bigint (20)  DEFAULT  NULL ,
       `tablename`  varchar (255)  COLLATE  utf8_unicode_ci  DEFAULT  NULL  COMMENT  '表名' ,
       `comment`  varchar (255)  COLLATE  utf8_unicode_ci  DEFAULT  NULL ,
       `dt` datetime  DEFAULT  NULL ,
       PRIMARY  KEY  (`tbid`)
    ) ENGINE=InnoDB AUTO_INCREMENT=27  DEFAULT  CHARSET=utf8  COLLATE =utf8_unicode_ci;
     
    -- ----------------------------
    -- Table structure for `tb_log_content`
    -- ----------------------------
    CREATE  TABLE  `tb_log_content` (
       `tbid`  bigint (20)  NOT  NULL  AUTO_INCREMENT,
       `logid`  bigint (20)  DEFAULT  NULL ,
       `tbkey` longtext  COLLATE  utf8_unicode_ci,
       `tbvalue` longtext  COLLATE  utf8_unicode_ci,
       `currenttbvalue` longtext  COLLATE  utf8_unicode_ci,
       `comment`  varchar (255)  COLLATE  utf8_unicode_ci  DEFAULT  NULL ,
       PRIMARY  KEY  (`tbid`)
    ) ENGINE=InnoDB AUTO_INCREMENT=109  DEFAULT  CHARSET=utf8  COLLATE =utf8_unicode_ci;


    展开全文
  • 数据库日志采集系统方案设计

    千次阅读 2022-01-07 13:33:19
    2021年我们研发了数据库日志采集分析平台系统,实现了数据库主机系统日志,数据库错误日志,审计日志的采集和分析。 01 背景介绍 之家数据库日志以前分散在各DB宿主机及容器RDS上,没有实现集中采集,排查定位故障...

    前言

    汽车之家几年前已实现了应用日志的采集,但是对数据库日志并没有实现集中采集, 排查定位数据库故障原因不便。2021年我们研发了数据库日志采集分析平台系统,实现了数据库主机系统日志,数据库错误日志,审计日志的采集和分析。

    01

    背景介绍

    之家数据库日志以前分散在各DB宿主机及容器RDS上,没有实现集中采集,排查定位故障原因不便。2021年DBA研发了数据库日志采集分析平台,实现了主机系统日志,数据库错误日志,审计日志的采集和分析。通过日志的集中采集,提升了故障排查效率,并且深入挖掘日志价值,可为未来之家数据库的智能审计,智能运维(如故障预警,自愈,自动调优)提供基础。

    02

    日志分析发展历程

    日志分析并不是一项新颖的技术了,需要的是实际解决问题的思路或工具,哪怕是一段只有20行的命令脚本都可以。日志分析的发展划可分成4个时代:

    1.石器时代 : 石器时代日志分析通常依靠Excel、终端命令(awk、grep、sort、uniq、wc等)。

    2. 铁器时代 :  铁器时代通常依靠脚本工具(自写工具)、简易交互式工具(logwatch、logparser)等。

    3. 工业时代 : 工作时代大家在做日志分析的时候相比前两个时代已经有了太多 的进步了,各种开源、免费、付费的软件可供选择,比如:Elastic系列、Splunk等等。

    4. 未来时代 : 这个时代大家在做日志分析时候我不知道会用到什么,但是从目前来看,机器学习、人工智能应该是核心之一。

    统一日志分析平台的工作在看来可以简单 分为2个阶段:

    平台实现: 日志规范化 --> 日志采集 --> 日志存储 --> 日志分析 --> 日志展示 --> 告警实现;

    平台优化: 这个阶段是对平台实现中的每一个步骤进行优化。

    以下对常见的开源日志采集工具做一 简单分析:

    03

    方案设计

    之家数据库日志平台,可以分为应用层,日志采集层,缓存层,解析层,存储层,展示层,系统架构如 图1 。

    图 1 日志采集架构图

    04

    方案实施

    通过之家二次开发的log-pilot日志采集工具,对数据库机器系统日志、DB错误日志、审计日志进行采集。采集的日志数据存入Kafka,为避免和其它业务日志相互影响, DBA采集申请了独立的KAFKA集群与Topic进行隔离。消费KAFKA数据采用Flink,对数据库日志数据进行处理,优化,精简,最终日志存入Elasticsearch。日志展示使用了开源的Kibana。 即数据流为日志源文件→ Log-pilot→ Kafka→ Flink→ ELasticsearch→ Kibana展示。

    采集的数据库日志有错误日志,审计日志等,各种日志数据格式不统一,我们进行了日志规范化处理。其中审计日志支持json格式,DBA开发了Flink Api,Flink任务启动加载引用python包进行json数据处理赋值给ES索引字段。

    另外为了高效运维管理数据库日志,做了如下工作:

    • 日志可视化配置管理: 开发了日志配置管理界面,可以对DB实例进行日志路径配置,日志采集启停。

    • 日志监控报警: 对ERROR或敏感数据审计日志,上报公司监控告警平台(AutoCMP),对重要日志信息及时发现并通知到告警接收人。

    05

    平台展示

    图 2 数据库物理实例日志采集

    图 3 采集日志Dashboard展示

    图 4 数据库日志报警

    06

    展望

    未来计划增加重点数据库的SQL日志采集,通过分析SQL对高危操作。比如:DROP/TRUNCATE,DELETE等SQL语句执行进行审计监控报警。对数据库中warning/error等问题进行分析治理,进一步提升数据库服务质量。

    展开全文
  • 系统日志方案设计

    千次阅读 2018-10-02 17:11:32
    前后端交互系统 前端感知异常与不感知异常的区分有多重方法:不同的异常类或者相同的类,不同的错误码区间等等; 有多种交互形式的开放系统
  • 业务应用系统的业务操作日志设计

    千次阅读 2020-02-22 16:03:34
    日志信息应包含如下内容: 1.操作时间 2.系统设备的主机名与IP地址(即被访问的服务器) 3.操作源IP(即访问者IP) 4.访问的业务名称 5.操作方式(增删改查) 6.操作结果(成功,失败) ...
  • 用户登录 日志设计

    万次阅读 2016-10-25 16:31:56
    设计: 脚本: USE [master] GO CREATE TABLE [dbo].[LogLoginOn]( [Id] [bigint] IDENTITY(1,1) NOT NULL, [InputLoginName] [varchar](50) NULL, [ClientIP] [varchar](15) NULL, [MemberId] [bigint]
  • 系统设计——操作日志

    万次阅读 2017-07-10 16:41:44
    系统操作日志原型设计: 该设计主要为实现用户操作一些重要功能过程中,系统会自动记载对应操作日志,以及操作过程中涉及的库、表、字段及数据变动,以便后期追责、及功能回退等需要。 名词解释: 业务编码: 指系统...
  • 文章目录目录日志结构的文件系统日志文件系统 日志结构的文件系统 技术的改变会给当前的文件系统带来压力。这种情况下,CPU 会变得越来越快,磁盘会变得越来越大并且越来越便宜(但不会越来越快)。内存容量也是以...
  • 背景对于很多后台管理系统来说,权限较多,对系统操作的人也会多。如此以来,对于一些操作的记录就非常有必要了,从而可以清楚的追踪对系统进行操作的人以及做了哪些操作,并且可以...下面简单说一下自己设计的思路。
  • 日志管理和分析系统设计与实现

    热门讨论 2009-03-31 15:46:17
    分析了日志格式一Windows操作系统事件日志、UNIX系统日志和通用防火墙日志。系统 通过采集、筛选分析法、特征匹配分析法、统计网络设备日志数据,并提供Web应用向 用户提供关于网络设备的运行状态和安全事件的统计...
  • 设计单片机日志系统

    千次阅读 2016-09-13 16:19:14
     环境:主机:WIN10开发环境:MDK5.12MCU:STM32F407说明:为单片机设计了一套简单的日志系统,通过日志系统提供的接口可以查看设备状态,并进行一些基本的调试。日志系统通过串口输出,所以单片机需要准备一个串口供...
  • ELK日志分析系统

    千次阅读 2022-04-25 21:57:05
    1、ELK日志分析系统
  • syslog日志系统——日志采集

    万次阅读 2018-11-08 14:00:17
    客户端埋点就是在客户系统植入日志发送的代码,可以是前端页面、app界面、后端服务等,把需要的日志信息发送到指定的日志采集接口。 日志的发送应该采用异步方式,这样不会对客户系统代码的执行造成影响。 日志采集 ...
  • LeetCode 635. 设计日志存储系统(map)

    千次阅读 2020-07-21 20:14:56
    文章目录1. 题目2. 解题 1. 题目 ...设计一个日志存储系统实现如下功能: void Put(int id, string timestamp):给定日志的 id 和 timestamp,将这个日志存入你的存储系统中。 int[] Retrieve
  • https://mp.weixin.qq.com/s/XiCky-Z8-n4vqItJVHjDIg
  • 日志结构文件系统设计与实现

    千次阅读 2017-12-06 22:34:34
    from The Design and Implementation of a Log-Structured File System——–Mendel Rosenblum...日志式文件系统将所有的更改以日志式的结构连续的写入磁盘,以这种方式来同时加速了文件写入和崩溃恢复。日志是磁盘的上
  • 操作系统课程设计

    千次阅读 热门讨论 2020-06-05 13:23:36
    操作系统课程设计汇总 来源:网络收集 这次海轰选择是题目二,感觉有点难,得费一点时间 其他的题目以后有时间可以尝试一下 再不写C++,就快凉了 题目一:支持多个进程(线程)并发运行的简单进程(线程)管理模拟...
  • 预写日志(WAL,Write-Ahead Log)将每次状态更新抽象为一个命令并追加写入一个日志中,这个日志只追加写入,也就是顺序写入,所以 IO 会很快。相比于更新存储的数据结构并且更新落盘这个随机 IO 操作,写入速度更快...
  • 操作日志设计

    万次阅读 2019-07-25 17:36:16
    管理系统的操作日志如何做成通用的模块一直是个让我头疼的问题,不过看了博客园里的某篇文章后,现在基本解决了。    在开始做之前,必须把两个日志分清楚,那就是普通操作日志和业务操作日志,这两者有何区别? ...
  • 操作日志产品设计

    千次阅读 2020-07-29 11:20:06
    操作日志是一个经常被忽视,但在关键时刻不被甩锅的好功能,那操作日志该怎么设计呢? 对开发而言,执行insert、update、delete这3个操作的时候,就需要进行日志,而日志执行的先后顺序如下: 对产品经理而...
  • 每个软件都有自己的日志系统,每种语言都有自己的日志框架/模块,随着互联网和大数据的蓬勃发展,分布式的日志系统,以及日志分析系统也应用的越来越广泛,越来越成熟。这里讨论的日志设计,主要关注日志的内容,而...
  • 系统日志的重要性

    千次阅读 2019-04-26 10:11:29
    系统日志的重要性  与一个简单的算法不同,一个合格的系统不仅仅要求具有运行的高效和计算的准确,同时又必须兼顾稳定性、可靠性。其次,对于开发人员来说,又必须具有可拓展性和可维护性。各方面都必须很完善,...
  • 预写日志(WAL,Write-Ahead Log)将每次状态更新抽象为一个命令并追加写入一个日志中,这个日志只追加写入,也就是顺序写入,所以 IO 会很快。相比于更新存储的数据结构并且更新落盘这个随机 IO 操作,写入速度更快...
  • 日志监控告警系统设计与实现

    千次阅读 2018-07-19 13:42:04
    基于的日志进行监控,监控需要一定规则,对触发监控规则的日志信息进行告警,告警的方式,是短信和邮件。 log4j----&gt;error,info,debug 应用程序程序的日志 error级别 TimeOutException 角标越界...
  • 日志系统之HBase日志存储设计优化

    万次阅读 2015-06-13 15:08:27
    本文探讨了基于HBase的日志存储原先自建索引存在的问题,展开分析了rowKey优化、索引优化等相关的优化策略。
  • Windows系统服务器系统日志在哪里查看? 服务器日志是什么? 服务器日志(server log)是一个或多个由服务器自动创建和维护的日志文件,其中包含其所执行活动的列表 简单来说,服务器的日记就是记录网站被访问的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 484,320
精华内容 193,728
关键字:

怎么设计系统的登录日志