#和##的使用以及思考c实现日志打印

简介: #和##的使用以及思考c实现日志打印

c语言日志模块的可实现方案:

内容摘自网络,梳理出来。。。

1:#和## 属于预处理标记: #后面转换为字符串 ##预处理拼接标记

2:VA_ARGS C99中新增的特性,支持宏定义中支持可变参数,接收…传递多个参数。

使用在使用了...的宏定义中:
  #define myprintf(...) fprintf(stderr, __VA_ARGS__)

3:如何解析不定参数? ==》应该是__VA_ARGS__ 底层实现,使用va_list

4:#VA_ARGS 仅仅展开列表对应的字符串了。

5:##VA_ARGS 是GUN的特性,c标准不建议这样用。

解决__VA_ARGS__参数个数为0时,宏定义最后的,会编译报错问题。

##在如果参数为0时,会把宏定义中前面的逗号删除掉。

6:c语言中printf输出其实就是上面的逻辑思路:

#include <stdio.h>
  int printf(const char *format, ...);               //输出到标准输出
  int fprintf(FILE *stream, const char *format, ...);        //输出到文件
  int sprintf(char *str, const char *format, ...);         //输出到字符串str中
  int snprintf(char *str, size_t size, const char *format, ...);   //按size大小输出到字符串str中
下面系列把一个个变量用va_list调用所替代:
  int vprintf(const char *format, va_list ap);
  int vfprintf(FILE *stream, const char *format, va_list ap);    
  int vsprintf(char *str, const char *format, va_list ap);
  int vsnprintf(char *str, size_t size, const char *format, va_list ap);

7:redis中分析:

#include <stdio.h>
  #define D(...)                                                               \
      do {                                                                     \
          FILE *fp = fopen("/tmp/log.txt","a");                                \
          fprintf(fp,"%s:%s:%d:\t", __FILE__, __func__, __LINE__);             \
          fprintf(fp,__VA_ARGS__);                                             \
          fprintf(fp,"\n");                                                    \
          fclose(fp);                                                          \
      } while (0);
  #define debugf(...)                                                            \
      if (raxDebugMsg) {                                                         \
          printf("%s:%s:%d:\t", __FILE__, __FUNCTION__, __LINE__);               \
          printf(__VA_ARGS__);                                                   \
          fflush(stdout);                                                        \
      }
  //epos是一个全局变量,每一次在调用该宏时,获取文件读写指针当前位置 ftello
  static char error[1044];
  static off_t epos;
  #define ERROR(...) { \
        char __buf[1024]; \
        snprintf(__buf, sizeof(__buf), __VA_ARGS__); \
        snprintf(error, sizeof(error), "0x%16llx: %s", (long long)epos, __buf); \
    }

8: 知识点样例代码

//对描述提到的问题进行测试
#include <stdio.h>
#include <stdarg.h>
//1:#的测试,转换为字符  可以用这种方式转换打印字符串
#define P(A)    printf("%s:%d\n",#A,A)
#define SQUARE(x)   printf("The square of "#x" is %d.\n", ((x)*(x)))
//其实也是个字符串拼接
#define MY_TOSTR(var) (#var"[test1]\n")
void test_1()
{
  P(88);  //把int 8转为字符直接输出
  //使用场景
  int a=11, b=12;
  P(a+b);
  //求8的平方
  SQUARE(8);
  printf("%s", MY_TOSTR(8888));
}
//2: ##的测试,可以实现字符的拼接,使用场景:自动生成代码,可以用来拼接函数名
// static const char* FUNC = "_func";
//这玩意是直接拼接参数的东东
#define my_math(x, y) (x##e##y)
//linux下直接用空格可以实现拼接
#define XNAME(name, type) #name #type
//linux下的拼接直接用一个空格
#define SOFTWARE_VERSION "Software:V1.00"
#define HARDWARE_VERSION "Hardware:V1.00"
#define SYSTEM_VERSION SOFTWARE_VERSION HARDWARE_VERSION
//TODO: linux下如何实现函数名的拼接?自动生成一些代码  可以参考thritf? 代码生成器相关
void test_2()
{
  printf("XNAME:%s \n", XNAME(44, 55));
  printf("version: [%s] \n", SYSTEM_VERSION);
  printf("%e \n", my_math(3, 4));
  //函数名拼接
  return;
}
//3: C99新增__VA_ARGS__,改变了可变参数不能用在宏中的定义。 
#define debug(...) printf(__VA_ARGS__)
void test_3()
{
  debug("test of __VA_ARGS__ [%d]:[%s] \n", 0, "test");
  debug("test of __VA_ARGS__ error \n");
}
//但是如果这样定义,会出错,因为当没有参数时,宏定义fmt后面的逗号是多余的
//fmt把输出的格式串提取出来了,上面是直接放在了...
#define debug1(fmt, ...) printf(fmt, __VA_ARGS__)
void test_3_1()
{
  debug1("test of __VA_ARGS__ [%d]:[%s] \n", 0, "test");
  // expected expression before ‘)’ token
  // debug1("test of __VA_ARGS__ error \n"); //这个应该会报错,因为多了一个逗号
}
// 4: 用##来优化test_3中把fmt提取出来,如果没有参数就会报错
// 其他思路: 实际的输出用printf系列的其他函数 也可以写入文件
#define debug2(fmt, ...) printf(fmt, ##__VA_ARGS__)
#define debug3(fmt, ...) fprintf (stderr, fmt, ##__VA_ARGS__)
void test_4()
{
  debug2("test of ##__VA_ARGS__ [%d]:[%s] \n", 0, "test");
  debug2("test of ##__VA_ARGS__ success \n");
  debug3("test of ##__VA_ARGS__ [%d]:[%s] \n", 0, "test");
  debug3("test of ##__VA_ARGS__ success \n");
}
// 5: va_list的测试 第一个参数是参数要相加的参数的个数
int sum(int num_args, ...)
{
   int val = 0;
   va_list ap;
   int i;
   va_start(ap, num_args);
   for(i = 0; i < num_args; i++)
   {
      val += va_arg(ap, int);
   }
   va_end(ap);
   return val;
}
void stdio_printf(const char* fmt, ...)
{
   int len, i;
   va_list ap;
   char buffer[256];
   va_start(ap, fmt);
   len = vsnprintf(buffer, sizeof(buffer), (const char*)fmt, ap);
   va_end(ap);
   printf("buffer: %s \n",buffer);
}
void test_5()
{
  printf("15 和 56 的和 = %d\n",  sum(2, 15, 56) );
  printf("add :%d \n", sum(3,2,3,4));
  stdio_printf("%s %d \n", "test_5", 5);
}
int main()
{
  //#的测试
  test_1();
  test_2();
  //##的测试 TODO linux下##来自动生成代码拼接函数名的实现
  test_3();
  test_3_1();
  test_4();
  test_5();
  return 0;
}

相关样例代码:

#include <stdio.h>
#define LOG(level, format, ...) \
        fprintf(stderr, "[%s|%s@%s:%d] " format "\n", \
            level, __func__, __FILE__, __LINE__, ##__VA_ARGS__ )

可写入文件:

#include <stdio.h>
#include <stdarg.h>
#include <time.h>
void logC(const char *func, const char *file, const int line,
          const char *type, const char *format, ...)
{
    FILE *file_fp;
    time_t loacl_time;
    char time_str[128];
    // 获取本地时间
    time(&loacl_time);
    strftime(time_str, sizeof(time_str), "[%Y.%m.%d %X]", localtime(&loacl_time));
    // 日志内容格式转换
    va_list ap;
    va_start(ap, format);
    char fmt_str[2048];
    vsnprintf(fmt_str, sizeof(fmt_str), format, ap);
    va_end(ap);
    // 打开日志文件
    file_fp = fopen("./main.log", "a");
    // 写入到日志文件中
    if (file_fp != NULL)
    {
        fprintf(file_fp, "[%s]%s[%s@%s:%d] %s\n", type, time_str, func, 
                file, line, fmt_str);
        fclose(file_fp);
    }
    else
    {
        fprintf(stderr, "[%s]%s[%s@%s:%d] %s\n", type, time_str, func, 
                file, line, fmt_str);
    }
}
#define LOGC(type, format, ...) logC(__func__, __FILE__, __LINE__, type, format, ##__VA_ARGS__)
int main()
{
    LOGC("LOG_DEBUG", "a=%d", 10);
    return 0;
}
相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
7月前
|
Java
解决logback不能打印日志的问题
解决logback不能打印日志的问题
280 0
|
2天前
|
Java
使用logback异步打印日志
使用logback异步打印日志
使用logback异步打印日志
|
2天前
|
Java Maven
maven 项目配置日志打印以及异常日志打印问题
maven 项目配置日志打印以及异常日志打印问题
78 0
|
7月前
|
Serverless Python
可以通过配置logging模块来记录日志
可以通过配置logging模块来记录日志
28 2
|
7月前
【日志配置】Logback-prod日志配置方式,配置方法,如何配置日志打印(二)
【日志配置】Logback-prod日志配置方式,配置方法,如何配置日志打印
33 0
|
XML JavaScript Java
可观测性之Log4j2优雅日志打印
可观测性之Log4j2优雅日志打印
可观测性之Log4j2优雅日志打印
|
11月前
|
消息中间件 缓存 监控
Log4J日志打印引发的OOM问题排查
Log4J日志打印引发的OOM问题排查
376 0
|
JSON Dart Android开发
日志输出
日志输出
217 0
OpenFeign超时控制和日志打印功能
OpenFeign默认等待1秒钟,超过后报错 现在假设8001端口有一个请求复杂的业务,需要的时间比较长。
OpenFeign超时控制和日志打印功能
|
消息中间件 监控 数据可视化
你真的会打印日志?
你真的会打印日志?
你真的会打印日志?