使用标准流实现的灵活C++日志记录器类

5

我希望创建一个灵活的日志记录器类。我想让它能够将数据输出到文件或标准输出。此外,我想使用流。该类应该像下面这样:

class Logger
{
private:
   std::ostream m_out; // or ofstream, iostream? i don't know
public:

   void useFile( std::string fname);
   void useStdOut();

   void log( symbol_id si, int val );
   void log( symbol_id si, std::string str );
   //etc..
};
symbol_id是一个枚举类型,用于定义格式。我想实现的目标是能够轻松地在标准输出和文件之间切换(这就是use*方法的目的)。最好只需使用m_out并简单地编写m_out << "something";,无需检查是否要写入文件或标准输出。

我知道有很多方法可以解决这个问题(使用if测试是否要写入文件或标准输出,使用“C方式”(使用FILE*fprintf)等),但我确信有一种使用C++流的好方法来实现这一点。但我似乎找不到如何做到这一点的方法。请问有人可以帮我吗?

4个回答

9

C++中的std::o*stream类继承自std::ostream。这意味着您应该根据std::ofstream指针或引用编写接口:

class Logger
{
    std::ostream *m_out; // use pointer so you can change it at any point
    bool          m_owner;
public:
    // constructor is trivial (and ommited)
    virtual ~Logger()
    {
        setStream(0, false);
    }
    void setStream( std::ostream* stream, bool owner )
    {
        if(m_owner)
            delete m_out;
        m_out = stream;
        m_owner = owner;
    }
    template<typename T>
    Logger& operator << (const T& object)
    {
        if(!m_out)
            throw std::runtime_error("No stream set for Logger class");
        (*m_out) << object;
        return *this;
    }
};

// usage:
Logger logger;
logger.setStream( &std::cout, false ); // do not delete std::cout when finished
logger << "This will be logged to std::cout" << std::endl;
// ...
logger.setStream( 
    new std::ofstream("myfile.log", std::ios_base::ate|std::ios_base::app), 
    true ); // delete the file stream when Logger goes out of scope
logger << "This will be appended to myfile.log" << std::endl;

谢谢。我也会点赞你的回答,因为它是正确的,但是the_mandrill更早发布了类似的内容,所以他获得了“被采纳的答案”标志 ;) - PeterK
@sree,这两个操作都不是线程安全的(即在竞争条件下,您可以向同一日志发送两条消息,您可以发送数据并更改日志指针,在竞争条件下您可以设置两个不同的日志流)。 - utnapistim
有错误![错误]无效使用不完整类型'std::ofstream {aka class std::basic_ofstream<char>}',不能传递额外参数。 - uss
@sree,对于那个错误,请添加#include <fstream>头文件。我不确定你所说的“不能传递额外参数”是什么意思(要向什么传递额外的参数?你想做什么?) - utnapistim

8

我之前解决这个问题的方式是创建一个抽象基类Logger,并创建单独的FileLoggerOutStreamLogger类。然后创建一个实现Logger接口的CompositeLogger对象,它只输出所有记录器:

CompositeLogger compLogger;
compLogger.Add(new FileLogger("output.txt"));
compLogger.Add(new StreamLogger(std::cout));
...
compLogger.log(...);

如果您不需要这种灵活性,希望将所有内容保留在单个类中,您可以将m_Out变量改为指向std :: ostream的指针,并添加额外的标志来跟踪是否需要在清除时删除它:

private:
  std::ostream*   m_out;
  bool            m_OwnsStream;

Logger() {
   m_Out=&std::cout; // defaults to stdout
   m_OwnsStream=false;
}
void useFile(std::string filename) {
  m_Out=new std::ofstream(filename);
  m_OwnsStream=true;
}
~Logger() {
  if (m_OwnStream) 
    delete m_Out; 
}

显然,你需要在useFileuseStdOut中添加更多的检查以防止内存泄漏。

4

2
一种与曼德里尔解决方案不同的方式,我认为策略模式在这个问题上更合适,从概念上来说。
只需调用context->SetLogger,我们就可以随时更改日志策略。
我们还可以为文件记录器使用不同的文件。
class Logger
{
protected:
    ostream* m_os;
public:
    void Log(const string& _s)
    {
        (*m_os) << _s;
        m_os->flush();
    }
};

class FileLogger : public Logger
{
    string m_filename;
public:
    explicit FileLogger(const string& _s)
    : m_filename(_s)
    {
        m_os = new ofstream(m_filename.c_str());
    }
    ~FileLogger()
    {
        if (m_os)
        {
            ofstream* of = static_cast<ofstream*>(m_os);
            of->close();
            delete m_os;
        }
    }
};

class StdOutLogger : public Logger
{
public:
    StdOutLogger()
    {
        m_os = &std::cout;    
    }
};

class Context
{
    Logger* m_logger;
public:
    explicit Context(Logger* _l)  {m_logger = _l;}
    void SetLogger(Logger* _l)    {m_logger = _l;}
    void Log(const string& _s)
    {
        if (m_logger)
        {
            m_logger->Log(_s);
        }
    }
};

int main()
{
    string filename("log.txt");

    Logger*  fileLogger   = new FileLogger(filename);
    Logger*  stdOutLogger = new StdOutLogger();
    Context* context      = new Context(fileLogger);

    context->Log("this log out to file\n");
    context->SetLogger(stdOutLogger);
    context->Log("this log out to standard output\n");
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接