杂谈
刚刚入行不久,想得太简单,出错了也知道哪里的问题,思维不够严密。加班熬夜少不了,写代码两小时,调试两整天。怀疑别人写的代码有问题,怀疑别人写的借口有问题,甚至于怀疑编译器。因为没有打印日志而选择弹框报错,报错信息不详尽,哪里出问题都不知道;没有catch错误,挨批了。我还以为catch C#才有的。
如何打印日志
- 在接口调用位置打日志
- 在关键函数位置打日志
- 在线程进程位置打日志
- 在句柄位置打日志
- 在关键信息的位置打日志
- 在数据库调用的位置打日志
- 在你怀疑的地方打日志
总之我们的程序运行情况也全都依赖日志 打日志也是门学问。前期可能会抓不住重点需要加很多日志。
我是搞C++MFC的我下面提供我写代码的日志函数,这个日志函数是有多个模式可以选择的
.cpp文件
/* 包含头文件 */
/*
日志文件的相关操作
*/
//#include "stdafx.h"
#include "pch.h"
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include "log.h"
#include <CONIO.H>
HANDLE hOutputHandle;
DWORD nRet;//用来记录实际输出的字符个数
// 内部常量定义
#define LOG_MODE_DISABLE 0
#define LOG_MODE_FILE 1
#define LOG_MODE_DEBUGER 2
#define LOG_MODE_CONSOLE 3
// 内部全部变量定义
static FILE *s_log_fp = NULL;
static DWORD s_log_mode = LOG_MODE_DISABLE;
/* 函数实现 */
void log_init(const char *file)
{
if (!s_log_fp) {
if (strcmp(file, "DEBUGER") == 0) {
s_log_mode = LOG_MODE_DEBUGER;//2
}
else if (strcmp(file, "CONSOLE") == 0) {
s_log_mode = LOG_MODE_CONSOLE;//3
AllocConsole(); //打开控制台
hOutputHandle = GetStdHandle(STD_OUTPUT_HANDLE);//标准输出句柄
}
else if (strcmp(file, "NULL") == 0) {
s_log_mode = LOG_MODE_DISABLE;//0
}
else {
s_log_fp = fopen(file, "w");
if (s_log_fp) {
s_log_mode = LOG_MODE_FILE;//1
}
}
}
}
void log_done(void)
{
if (s_log_fp) {
fflush(s_log_fp);
fclose(s_log_fp);
s_log_fp = NULL;
s_log_mode = 0;
}
if(s_log_mode = LOG_MODE_CONSOLE) FreeConsole(); //关闭后台
}
#define MAX_LOG_BUF 1024
//定义一个使用省略号的函数原型
//打印信息加日期
void log_printf(const char *format, ...)
{
// if debug log is not enable, directly return
//模式0返回
if (s_log_mode == LOG_MODE_DISABLE) return;
char buf[MAX_LOG_BUF];
char OutBuf[MAX_LOG_BUF+32];
//声明一个va_list类型的变量valist
va_list valist;
//使用va_star把变量ap初始化为参数列表,指向参数列表的第一个可选参数
va_start(valist, format);
//第一个参数缓冲区
//第二个参数缓冲区大小
//第三个参数是参数列表
//第四个参数valist是指向可选参数的指针
//format内容合并到buf中
vsnprintf(buf, MAX_LOG_BUF, format, valist);
//清空参数列表,并使valist无效
va_end(valist);
SYSTEMTIME st;
CString strDate;
GetLocalTime(&st);
//格式化日期
strDate.Format("[%4d-%02d-%02d %02d:%02d:%02d:%03d]",st.wYear,st.wMonth,st.wDay, st.wHour,st.wMinute,st.wSecond,st.wMilliseconds);
//格式化日期和变参内容到OutBuf中
sprintf(OutBuf, "%s %s", (char *)(LPCTSTR)strDate, buf);
switch (s_log_mode)//模式选择
{
case LOG_MODE_FILE: //模式1
//不加换行符
//缓冲区
//文件指针
fputs(OutBuf, s_log_fp);
//刷新缓冲区
fflush(s_log_fp);
break;
case LOG_MODE_DEBUGER: //模式2
//输出调试信息
OutputDebugStringA(OutBuf);
break;
case LOG_MODE_CONSOLE: //模式3
//句柄
//缓冲区
//大小in
//输出的实际大小out
//保留位
WriteConsole(hOutputHandle, OutBuf, strlen(OutBuf), &nRet, NULL);
break;
}
}
.h文件
#pragma once
void log_init (const char *file);
void log_done (void);
void log_printf(const char *format, ...);
举一个例子在配置文件里面选择日志输出的方式:
配置文件中选择日志模式需添加的函数
/静态函数--得到了文件所在的路径
static void get_app_dir(char *path, int size)
{
HMODULE handle = GetModuleHandle(NULL); //获取进程空间的句柄
GetModuleFileNameA(handle, path, size); // path得到文件的全路径
// ps:D:\base\file\fliename.txt ->D:\base\file
char *str = path + strlen(path);
while (*--str != '\\');
*str = '\0';
}
//静态函数————得到了赋予给关键字的值
static void parse_params(const char *str, const char *key, char *val)
{
char *p = (char*)strstr(str, key); //判断key是否是子串,返回子串以及后面的全部字符。
int i;
if (!p) return;
p += strlen(key); //跳过关键字
if (*p == '\0') return;
while (1) {
if (*p != ' ' && *p != '=' && *p != ':') break; //空格等于冒号就继续下移,否则跳出
else p++;
}
for (i = 0; i < MAX_PATH; i++) {
if (*p == ',' || *p == ';' || *p == '\r' || *p == '\n' || *p == '\0') {//找到完了跳出
val[i] = '\0';
break;
}
else {
val[i] = *p++; //值放到了val数组中
}
}
}
//静态函数------给关键字初始化,值从配置文件里面读出来的
static int load_config_from_file(char *mode)
{
char file[MAX_PATH];
FILE *fp = NULL;
char *buf = NULL;
int len = 0;
// open params file
get_app_dir(file, MAX_PATH); //路径放到了file中
strcat(file, "\\mymode.ini"); //路径+文件名字
fp = fopen(file, "rb"); //二进制打开文本只读
if (fp) {
fseek(fp, 0, SEEK_END); //定位文件指针到文本末尾
len = ftell(fp); //返回文件指针的位置,相当于开头到这里的偏移量
buf = (char*)malloc(len);
if (buf) {
fseek(fp, 0, SEEK_SET); //定位文件指针到开头
fread(buf, len, 1, fp); //读全文
//给关键字初始化,值从配置文件里面读出来的
parse_params(buf, "loginmode", mode);
free(buf);
}
fclose(fp);
return 0;
}
return -1;
}
调用方式
strcpy(m_strLogFile, "CONSOLE"); //m_strLogFile 是char*类型的,默认模式控制台
int ret = load_config_from_file(m_strLogFile); //从配置文件里面读取信息
if (ret != 0) {
AfxMessageBox(TEXT("无法打开测试配置文件!"), MB_OK);
}
log_init(m_strLogFile);//配置模式
log_printf("logfile = %s\n", m_strLogFile); //函数于printf是使用方法一样
log_printf("InitDlg----------\n");
运行结果:
配置了后台打印
配置了打印到文本,配置文件中的字符,就是日志文件名。
C++捕抓异常try catch
下面是一个捕获异常的模板,可以也推荐和打印日志使用,在容易出错的地方加上try catch,很省事情。
往往对数据库函数的调用应该用try catch 要进行容错处理。
#include <iostream>
#include <stdexcept> //捕获异常的头文件
using namespace std;
int main()
{
try
{
//...操作
}
catch (exception e)
{
log_printf("....操作捕获到异常:%s", e.what()); //捕获到的异常打印到日志中
}
}
局限性:一般崩溃无非就是数组越界或者空指针 这两种是catch不到的。