gabime / spdlog

Fast C++ logging library.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How can i use spdlog in C environment, can someone have ideas about how to pack it in <C&C++> environment

mandy-yan opened this issue · comments

my project has C and C++ files, How i can pack spdlog that i can use it in C and C++ files
Thanks!

commented

This is a general question for a mixed C and C++ project.
Expose functions that are compatible with C with extern "C". And call the C++ library within that function.

See also: https://isocpp.org/wiki/faq/mixing-c-and-cpp

commented

spdlog logging function is defined as a C++ template function.
Of course, template functions cannot be defined in C, so if you want to pass variable number of arguments., you have to define wrapper functions for all patterns.

commented

Unfortunately, the only way to do this is to define all functions that accept the necessary arguments.

commented

Please provide the code that is failing to compile.

commented

You can call the spdlog function from a C++ function that is compatible with the C.
I think you have a problem with the source code failing to compile, but I cannot investigate the cause as it is not provided.

commented

Please also provide compile error messages.

commented

I create small repo https://github.com/tt4g/spdlog_issue_2301, it's works for me.

I create small repo https://github.com/tt4g/spdlog_issue_2301, it's works for me.

I have four files in logs directory within the project, they are "Cspdlog.cpp Cspdlog.h CspdlogWrapper.cpp CsplogWrapper.h"

the main problem is here:
i can`t pass the "..." into the spdlog library from the CsplogWrapper.h

void nclog_debug_fmt(const char *fmt, ...)
{
    auto logger = SLog::CLogger::getInstance()->getNConsoleLogger().get();
    
    (logger)->log(spdlog::source_loc{__FILE__, __LINE__, SPDLOG_FUNCTION}, spdlog::level::debug, __VA_ARGS__);

}
void nclog_info_fmt(const char *fmt, ...)
{
    auto logger = SLog::CLogger::getInstance()->getNConsoleLogger().get();

    va_list args;
    va_start(args, fmt);
    (logger)->log(spdlog::source_loc{__FILE__, __LINE__, SPDLOG_FUNCTION}, spdlog::level::info, va_arg(fmt, args));
    va_end(args);
}

And the function "nclog_debug_fmt and nclog_info_fmt" is defined as this in CsplogWrapper.h:

#pragma once

#define NCLOG_DEBUG(fmt, log_arg...)                   nclog_debug_fmt(fmt, ##log_arg)
#define NCLOG_INFO(fmt, log_arg...)                     nclog_info_fmt(fmt, ##log_arg)
#define NCLOG_WARN(fmt, log_arg...)                    nclog_warn_fmt(fmt, ##log_arg)

#define CLOG_DEBUG(fmt, log_arg...)                    clog_debug_fmt(fmt, ##log_arg)
#define CLOG_INFO(fmt, log_arg...)                     clog_info_fmt(fmt,##log_arg)
#define CLOG_WARN(fmt, log_arg...)                     clog_warn_fmt(fmt,##log_arg)

Please also provide compile error messages.
the errors is below:

mipsel-openwrt-linux-uclibc-g++ -std=c++11 -I. -I./src -I./src/c-log/src -I./src/join_manager/ -I./src/jsoncpp-master/include -I./src/jsoncpp-master/src/lib_json -I./src/simple-timing-wheel -Wp,-MT,out/target/RiCiDb.o -Wp,-MMD,out/target/RiCiDb.o.d -g -Wall -Wno-unused -Wno-format -O2  -c -o out/target/RiCiDb.o ./src/RiCiDb.cpp
mipsel-openwrt-linux-uclibc-g++ -std=c++11 -I. -I./src -I./src/c-log/src -I./src/join_manager/ -I./src/jsoncpp-master/include -I./src/jsoncpp-master/src/lib_json -I./src/simple-timing-wheel -Wp,-MT,out/target/CspdlogWrapper.o -Wp,-MMD,out/target/CspdlogWrapper.o.d -g -Wall -Wno-unused -Wno-format -O2  -c -o out/target/CspdlogWrapper.o ./src/c-log/src/CspdlogWrapper.cpp
./src/c-log/src/CspdlogWrapper.cpp:10:98: warning: __VA_ARGS__ can only appear in the expansion of a C99 variadic macro [enabled by default]
     (logger)->log(spdlog::source_loc{__FILE__, __LINE__, SPDLOG_FUNCTION}, spdlog::level::debug, __VA_ARGS__);
                                                                                                  ^
./src/c-log/src/CspdlogWrapper.cpp: In function 'void nclog_debug_fmt(const char*, ...)':
./src/c-log/src/CspdlogWrapper.cpp:10:98: error: '__VA_ARGS__' was not declared in this scope
In file included from ./src/c-log/src/CspdlogWrapper.cpp:1:0:
./src/c-log/src/CspdlogWrapper.cpp: In function 'void nclog_info_fmt(const char*, ...)':
./src/c-log/src/CspdlogWrapper.cpp:19:109: error: expected type-specifier before 'args'
     (logger)->log(spdlog::source_loc{__FILE__, __LINE__, SPDLOG_FUNCTION}, spdlog::level::info, va_arg(fmt, args));
                                                                                                             ^
./src/c-log/src/CspdlogWrapper.cpp:19:109: error: expected ')' before 'args'
./src/c-log/src/CspdlogWrapper.cpp:19:114: error: expected ';' before ')' token
     (logger)->log(spdlog::source_loc{__FILE__, __LINE__, SPDLOG_FUNCTION}, spdlog::level::info, va_arg(fmt, args));
                                                                                                                  ^
In file included from ./src/c-log/src/CspdlogWrapper.cpp:1:0:
./src/c-log/src/CspdlogWrapper.cpp: In function 'void nclog_warn_fmt(const char*, ...)':
./src/c-log/src/CspdlogWrapper.cpp:28:109: error: expected type-specifier before 'args'
     (logger)->log(spdlog::source_loc{__FILE__, __LINE__, SPDLOG_FUNCTION}, spdlog::level::warn, va_arg(fmt, args));
                                                                                                             ^
./src/c-log/src/CspdlogWrapper.cpp:28:109: error: expected ')' before 'args'
./src/c-log/src/CspdlogWrapper.cpp:28:114: error: expected ';' before ')' token
     (logger)->log(spdlog::source_loc{__FILE__, __LINE__, SPDLOG_FUNCTION}, spdlog::level::warn, va_arg(fmt, args));
                                                                                                                  ^
In file included from ./src/c-log/src/CspdlogWrapper.cpp:1:0:
./src/c-log/src/CspdlogWrapper.cpp: In function 'void mlog_warn_fmt(const char*, ...)':
./src/c-log/src/CspdlogWrapper.cpp:114:109: error: expected type-specifier before 'args'
     (logger)->log(spdlog::source_loc{__FILE__, __LINE__, SPDLOG_FUNCTION}, spdlog::level::warn, va_arg(fmt, args));
                                                                                                             ^
./src/c-log/src/CspdlogWrapper.cpp:114:109: error: expected ')' before 'args'
./src/c-log/src/CspdlogWrapper.cpp:114:114: error: expected ';' before ')' token
     (logger)->log(spdlog::source_loc{__FILE__, __LINE__, SPDLOG_FUNCTION}, spdlog::level::warn, va_arg(fmt, args));
                                                                                                                  ^
Makefile:147: recipe for target 'out/target/CspdlogWrapper.o' failed

I create small repo https://github.com/tt4g/spdlog_issue_2301, it's works for me.

my use is a little diffrent with your repo. Below is my use, and i don`t know how to reslove the problem.
the Cspdlog.cpp and Csplog.h i use the single instance C++ to wrap the spdlog, it just like this:

Cspdlog.h :
#pragma once

#include <iostream>
#include <vector>
#include "platform.h"
#include "spdlog/spdlog.h"
#include "spdlog/async.h"
#include "spdlog/sinks/stdout_sinks.h"
#include "spdlog/sinks/rotating_file_sink.h"


namespace SLog {
    
    //CLogger向STDOUT输出内容, 最低等级Debug
    class CLogger
    {
        public:
            static CLogger *getInstance();
            std::shared_ptr<spdlog::logger> getConsoleLogger();
            std::shared_ptr<spdlog::logger> getNConsoleLogger();

        private:
            CLogger();
            ~CLogger();
            CLogger(const CLogger&)=delete;
            CLogger& operator=(const CLogger&)=delete;

        public:
            void setPattern(const char *pattern);
            void setLevel(spdlog::level::level_enum level);

        public:
            spdlog::sink_ptr nconsole_sink;
            spdlog::sink_ptr console_sink;
        private:
            std::shared_ptr<spdlog::logger> console;
            std::shared_ptr<spdlog::logger> npattern_console;
    };
}
Cspdlog.cpp:
#include "Cspdlog.h"
#include "platform.h"

/*-----------------CLogger----------------------*/
CLogger::CLogger()
{
    try
    {        
        nconsole_sink = std::make_shared<spdlog::sinks::stdout_sink_mt>();
        nconsole_sink->set_level(spdlog::level::debug);		         //NConsole默认为Debug
        nconsole_sink->set_pattern("%v");                            //NConsole格式为空

        console_sink = std::make_shared<spdlog::sinks::stdout_sink_mt>();
        console_sink->set_level(spdlog::level::debug);		        //Console默认为Debug
        console_sink->set_pattern("[%D %T %!:%#][%L] %v");          //Console默认为"%D %T %!:%#][%L] %v"

        console = std::make_shared<spdlog::logger>("console", console_sink);
        npattern_console = std::make_shared<spdlog::logger>("npatternconsole", nconsole_sink);

        npattern_console->set_level(spdlog::level::debug);          //不带格式化输出数据, 在指定处使用
        npattern_console->set_pattern("%v");
        spdlog::register_logger(npattern_console);

        console->set_level(spdlog::level::debug);                   //带格式化输出数据
        console->set_pattern("[%D %T %!:%#][%l] %v");
        spdlog::register_logger(console);

        spdlog::set_default_logger(console);
    }
    catch(const spdlog::spdlog_ex& ex)
    {
        std::cout << "spdLog initialization failed: " << ex.what() << std::endl;
    }
}

CLogger::~CLogger()
{
    spdlog::drop("console");
    spdlog::drop("npatternconsole");
}

CLogger *CLogger::getInstance()
{
    static CLogger clogger;
    return &clogger;
}

std::shared_ptr<spdlog::logger> CLogger::getConsoleLogger()
{
    return console;
}

I don't know whether I have described clearly, if you have something unclearly, pls. told me
many thanks to you.

commented

C++ template arguments must be passed type information.
You cannot use va_list and va_args because they do not provide type information.
Don't use va_list and va_args. and the only way is to define a different C-compatible function for each type of logging argument.

commented

I create small repo https://github.com/tt4g/spdlog_issue_2301, it's works for me.

I have created the above repository as sample code.

There are two rules that must be understood:

  1. Cannot use va_list and va_args for logging functions
  2. There is no function overloading in C

This means that the logging macros cannot be used (such as NCLOG_DEBUG and NCLOG_INFO).
Define all the differently named functions as follows, with only the required argument types:

void nclog_debug_fmt_char(const char *fmt, const char *extra)
{
    auto logger = SLog::CLogger::getInstance()->getNConsoleLogger().get();
    
    (logger)->log(spdlog::source_loc{__FILE__, __LINE__, SPDLOG_FUNCTION}, spdlog::level::debug, fmt, extra);

}

void nclog_debug_fmt_int(const char *fmt, int extra)
{
    auto logger = SLog::CLogger::getInstance()->getNConsoleLogger().get();
    
    (logger)->log(spdlog::source_loc{__FILE__, __LINE__, SPDLOG_FUNCTION}, spdlog::level::debug, fmt, extra);

}

void nclog_info_char(const char *fmt, const char *extra)
{
    auto logger = SLog::CLogger::getInstance()->getNConsoleLogger().get();

    (logger)->log(spdlog::source_loc{__FILE__, __LINE__, SPDLOG_FUNCTION}, spdlog::level::info, fmt, extra);
}

void nclog_info_int(const char *fmt, int extra)
{
    auto logger = SLog::CLogger::getInstance()->getNConsoleLogger().get();

    (logger)->log(spdlog::source_loc{__FILE__, __LINE__, SPDLOG_FUNCTION}, spdlog::level::info, fmt, extra);
}

'nclog_debug_fmt_char' means just print the string?
'nclog_debug_fmt_int' means just print the integer?

But if i want print variable parameters, How it use? ex:
spdlog::debug("an example to print string {}, print {}", "test",100);

commented

'nclog_debug_fmt_char' means just print the string?
'nclog_debug_fmt_int' means just print the integer?

Yes.

But if i want print variable parameters, How it use?

As written previously, define all functions that correspond to the argument types you want to output to the log.

Example:

void nclog_debug_fmt_char_int(const char *fmt, const char *str, int num)
{
    auto logger = SLog::CLogger::getInstance()->getNConsoleLogger().get();
    
    (logger)->log(spdlog::source_loc{__FILE__, __LINE__, SPDLOG_FUNCTION}, spdlog::level::debug, fmt, str, num);

}
commented

is this the only solution?

Yes.

commented

This is because the C++ template function performs type checking.
Learning the C++ template will help you understand why.

This is because the C++ template function performs type checking. Learning the C++ template will help you understand why.

if the function 'nclog_debug_fmt_char' works, then below codes maybe works too:

void nclog_info_fmt(const char *fmt, ...)
{
    char *buffer = NULL;

    buffer = (char *)malloc(1024);
    if(buffer == NULL)
        return;

    auto logger = SLog::CLogger::getInstance()->getNConsoleLogger().get();

    va_list args;
    va_start(args, fmt);
    vsprintf(buffer,fmt, args);
    (logger)->log(spdlog::source_loc{__FILE__, __LINE__, SPDLOG_FUNCTION}, spdlog::level::info, fmt, buffer);
    va_end(args);
    free(buffer);
}
commented

Since printf is a function that infers the type of its argument from the format string (first argument), it is a source of vulnerability. It is not a secure function.
spdlog does not support the printf style. If you need such a function, you must implement it at your own risk.

is there some function in spdlog can let me pass the variable parameter into the function 'log' in spdlog like the function vsprintf

commented

is there some function in spdlog can let me pass the variable parameter into the function 'log' in spdlog like the function vsprintf

No.

This is because the C++ template function performs type checking. Learning the C++ template will help you understand why.

if the function 'nclog_debug_fmt_char' works, then below codes maybe works too:

void nclog_info_fmt(const char *fmt, ...)
{
    char *buffer = NULL;

    buffer = (char *)malloc(1024);
    if(buffer == NULL)
        return;

    auto logger = SLog::CLogger::getInstance()->getNConsoleLogger().get();

    va_list args;
    va_start(args, fmt);
    vsprintf(buffer,fmt, args);
    (logger)->log(spdlog::source_loc{__FILE__, __LINE__, SPDLOG_FUNCTION}, spdlog::level::info, fmt, buffer);
    va_end(args);
    free(buffer);
}

pls, this way does it can work?, and if i use this way, i must use ‘%d %s’ instead of ‘{}’ yes?

commented

this way does it can work?, and if i use this way, i must use ‘%d %s’ instead of ‘{}’ yes?

If fmt contains {}, buffer will be output, but buffer is empty. And va_args will not work.
That would not be the behavior you expected.

As I have told you several times, it is impossible to pass logging arguments to spdlog using variable parameter in C.
It is recommended that you develop a deeper understanding of C++.

OK. I know. thanks you

@mandy-yan

template<typename... Args>
using format_string_t = fmt::format_string<Args...>;

template<typename... Args>
inline void info(int level, const char* tag, const char* file, const char* func, const long line, format_string_t<Args...> fmt, Args &&... args)
{
std::string info = fmt::format("{} {} {} {} {}", tag, file, func, line, fmt::format(fmt, std::forward(args)...));
your_log_api(level, info);
}
这样试试?

#define elog_critical(tag, ...)
info(ELOG_LVL_CRITICAL, tag, FILE, FUNCTION, LINE, VA_ARGS)

  1. In this way, the performance is a little slower, with about 800000 disks falling per second.