Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simple Logger #30

Merged
merged 10 commits into from
Jan 18, 2025
75 changes: 75 additions & 0 deletions src/utility/Logger.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* @file Logger.cpp
* @author Anand Doshi
* @date 2025-01-08
*
* @copyright Copyright (c) 2025
*
*/


#include "Logger.hpp"
#include "Exceptions.hpp"
#include <cstdio>
#include <ctime>
#include <iterator>
#include <stdexcept>
#include <string>
#include <string_view>

bool Logger::isLoggable(Level level)
{
if (filter == Filter::quiet)
{
return false;
}

if (filter == Filter::important)
{
return level == Level::error || level == Level::warning || level == Level::severe;
}

if (filter == Filter::verbose)
{
return true;
}

return true; // unknown filter, everything logged
}

constexpr std::string_view Logger::getLevelName(Logger::Level level)
{
switch (level)
{
case Logger::Level::error:
return "error";
case Logger::Level::warning:
return "warning";
EdwardPalmer99 marked this conversation as resolved.
Show resolved Hide resolved
case Logger::Level::severe:
return "severe";
case Logger::Level::info:
return "info";
case Logger::Level::fine:
return "fine";
case Logger::Level::debug:
return "debug";
default:
ThrowException("Invalid Level enum.");
}
}

void Logger::log(Level level, const char *file, unsigned int line, const char *func, std::string_view message)
{
if (!isLoggable(level))
return;

std::time_t now = std::time(nullptr);

char timestamp[std::size(Logger::timestampFormat)];
std::strftime(timestamp, std::size(Logger::timestampFormat),
"%FT%TZ", std::localtime(&now));

char outputInfo[1024];
snprintf(outputInfo, 1024, "[%s](%s)[%s]@%s:%d:%s() ", timestamp, name.c_str(), getLevelName(level).data(), file, line, func);
logStream << outputInfo << message << '\n';
}
72 changes: 72 additions & 0 deletions src/utility/Logger.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* @file Logger.hpp
* @author Anand Doshi
* @date 2025-01-08
*
* @copyright Copyright (c) 2025
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've done some more thinking. I think the logger should be a singleton. We need an instance() public method and the constructor should be protected to prevent initialisation by the user. Then the info, error, ... methods become static methods calling instance().log(...)

*
*/

#pragma once

#include <iostream>
EdwardPalmer99 marked this conversation as resolved.
Show resolved Hide resolved
#include <string_view>

class Logger
{
public:
enum class Filter
{
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the Filter is overkill. Personally, I would just use a second Level variable. Then you can compare the level of the message being passed in to the level of our Level variable since enum values are just increasing integers.

quiet,
verbose,
important
};

enum class Level
{
debug,
fine,
info,
severe,
warning,
error
};

EdwardPalmer99 marked this conversation as resolved.
Show resolved Hide resolved
Logger(std::string_view name)
: Logger{name, Filter::important, std::cout}
{
}

Logger(std::string_view name, Filter filter)
: Logger{name, filter, std::cout}
{
}

Logger(std::string_view name, Filter filter, std::ostream &logStream)
: name{name}, filter{filter}, logStream{logStream}
{
}

Logger() = delete;

void log(Level level, const char *file, unsigned int line, const char *func, std::string_view message);

inline void error(const char *file, unsigned int line, const char *func, std::string_view message) { log(Level::error, file, line, func, message); };
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks a little messy doesn't it? I think specifying the file, line, func was a mistake on my part. Also probably overkill on the log options: [debug, info, warning, error] are enough I think

inline void warning(const char *file, unsigned int line, const char *func, std::string_view message) { log(Level::warning, file, line, func, message); };
inline void severe(const char *file, unsigned int line, const char *func, std::string_view message) { log(Level::severe, file, line, func, message); };
inline void info(const char *file, unsigned int line, const char *func, std::string_view message) { log(Level::info, file, line, func, message); };
inline void fine(const char *file, unsigned int line, const char *func, std::string_view message) { log(Level::fine, file, line, func, message); };
inline void debug(const char *file, unsigned int line, const char *func, std::string_view message) { log(Level::debug, file, line, func, message); };

private:
const std::string name;
const Filter filter;
std::ostream &logStream;

// ISO 8601 date time format
inline static const std::string timestampFormat{"yyyy-mm-ddThh:mm:ssZ"};

bool isLoggable(Level level);

static constexpr std::string_view getLevelName(Level level);
};
Loading