rdaly525 / coreir

BSD 3-Clause "New" or "Revised" License
98 stars 24 forks source link

Logging Infrastructure #634

Open leonardt opened 5 years ago

leonardt commented 5 years ago

I'd like to add some logging infrastructure so we can more easily develop/debug with something more flexible than using std::cout and comments.

My initial search reveals: https://github.com/gabime/spdlog as a header file library option.

I have no experience with C++ loggers, so welcome to suggestions.

rdaly525 commented 5 years ago

I would definitely be open to adding this.

rsetaluri commented 5 years ago

I wrote a very simple version of GLOG once. Here are the files...not sure where you want them to go.

logging_lite.hpp:

// Copyright: Raj Setaluri 2017
// Author: Raj Setaluri (raj.setaluri@gmail.com)

// This file include a lightweight implementation of the glog LOG macro.

#ifndef COMMON_LOG_LOGGING_LITE_HPP_
#define COMMON_LOG_LOGGING_LITE_HPP_

#include <iostream>

enum LogSeverity {
  INFO = 0,
  FATAL
};

namespace common {
namespace internal {

// LoggerWrapper is a thread safe class.
class LoggerWrapper {
 public:
  LoggerWrapper() = default;
  explicit LoggerWrapper(bool abort) : abort_(abort) {}
  bool abort() const { return abort_; }
 private:
  bool abort_ = false;
};

class Logger {
 public:
  Logger(bool alive, bool abort) : alive_(alive), abort_(abort) {}
  Logger(Logger&& that) : alive_(true), abort_(that.abort_) {
    that.alive_ = false;
  }
  Logger(const Logger& that) = delete;
  ~Logger() {
    if (alive_) {
      EndLine();
      if (abort_) {
        Write("Check failed! aborting.");
        EndLine();
        abort();
      }
    }
  }

  template<typename T>
  static void Write(const T& x) { std::cout << x; }
  static void EndLine() { std::cout << std::endl; }

 private:
  bool alive_;
  bool abort_;
};

template<typename T> Logger operator<<(Logger&& l, const T& x) {
  Logger::Write(x);
  return std::move(l);
}

template<typename T> Logger operator<<(LoggerWrapper l, const T& x) {
  return std::move(Logger(true, l.abort()) << x);
}

class LoggerVoidify {
 public:
  LoggerVoidify() {}
  template<class T> void operator&(T& x) {}
  template<class T> void operator&(T&& x) {}
};

}  // namespace internal
}  // namespace common

#define LOG(severity)                                                   \
  ::common::internal::LoggerWrapper(severity == FATAL)                  \
      << __FILE__ << ":" << __LINE__ << " "

#define LOG_IF(severity, condition)                                     \
  (!condition) ? ((void) 0) :                                           \
      ::common::internal::LoggerVoidify() & LOG(severity)

#define CHECK(condition) LOG_IF(FATAL, !(condition))

#ifndef NDEBUG
#define DCHECK(condition) CHECK(condition)
#else  // !NDEBUG
#define DCHECK(condition) while(false) CHECK(condition)
#endif  // NDEBUG

#endif  // COMMON_LOG_LOGGING_LITE_HPP_

Hopefully doesn't depend on c++14...

rsetaluri commented 5 years ago

sample usage:

  LOG(INFO) << "some message";
  LOG_IF(INFO, true) << "should see this";
  LOG_IF(INFO, false) << "shouldn't see this";
  CHECK(true) << "shouldn't see this";
  CHECK(false) << "should see this and crash!";
leonardt commented 5 years ago

One features that would more useful that I use often in Python is a logger hierarchy or namespace. This allows you to set log parameters for specific portions of the code base. For example, it would be nice to configure, say via an environment variable, COREIR_LOG_DEBUG_VERILOG=1, which would name debug logging just for the verilog backend namespace, but wouldn't turn on debug logging for the entire code base.

rsetaluri commented 5 years ago

How do you achieve that in magma/python? we could likely do something similar if we use scoped objects (or use defer pattern) and a global stack.

leonardt commented 5 years ago

In magma.logging, we define a top level logger

import logging
import os

log = logging.getLogger("magma")

level = os.getenv("MAGMA_LOG_LEVEL", "INFO")
if level in ["DEBUG", "WARN", "INFO"]:
    log.setLevel(getattr(logging, level))
elif level is not None:
    logging.warning(f"Unsupported value for MAGMA_LOG_LEVEL: {level}")

Then in the coreir backend, we define a child logger

import logging
import os

logger = logging.getLogger('magma').getChild('coreir_backend')
level = os.getenv("MAGMA_COREIR_BACKEND_LOG_LEVEL", "WARN")
# TODO: Factor this with magma.logging code for debug level validation
if level in ["DEBUG", "WARN", "INFO"]:
    logger.setLevel(getattr(logging, level))
elif level is not None:
    logger.warning("Unsupported value for MAGMA_COREIR_BACKEND_LOG_LEVEL:"
                   f" {level}")

So we can set MAGMA_LOG_LEVEL=DEBUG to turn on debug logging everywhere, or MAGMA_COREIR_BACKEND_LOG_LEVEL=DEBUG to only turn it on in the coreir backend.

The main mechanism is to grabbing logger objects (that have a hierarchy)

logger = logging.getLogger('magma').getChild('coreir_backend')

and then the ability to configure specific objects

logger.setLevel

Then the code in the module only refers to the logger available to them (therefor using the configuration set for the logger that was referenced for that module)

rdaly525 commented 5 years ago

Added by #645