Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
Language Features:

Compiler Features:
* Commandline Interface: Add logging options `--log-level`, `--log` and `--log-output` to set the global default level, specific logger level and the output.
* Logging: Introduce runtime switchable logging functionality to the compiler. Loggers are declared at call sites with `DEFINE_LOGGER` and used via the `solTrace`/`solDebug`/`solWarn` macros.

Bugfixes:

Expand Down
2 changes: 2 additions & 0 deletions libsolutil/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ set(sources
Keccak256.h
LazyInit.h
LEB128.h
Logger.cpp
Logger.h
Numeric.cpp
Numeric.h
picosha2.h
Expand Down
141 changes: 141 additions & 0 deletions libsolutil/Logger.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
This file is part of solidity.

solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0

#include <libsolutil/Logger.h>

#include <iostream>

using namespace solidity::log;

namespace solidity::log
{

/// Cached logger handle owned by the registry.
/// Responsible for state changes (level) of the logger and its hierarchy.
struct LoggerHandle
{
LoggerHandle(std::string _category, Level _level):
category(std::move(_category)),
effectiveLevel(_level),
logger(&effectiveLevel, category)
{}

std::string category;
Level effectiveLevel;
Logger logger;
};

}

std::string_view solidity::log::levelToString(Level _level) noexcept
{
switch (_level)
{
case Level::trace: return "trace";
case Level::debug: return "debug";
case Level::warn: return "warn";
case Level::off: return "off";
}
return "off";
}

std::optional<Level> solidity::log::levelFromString(std::string_view _name)
{
if (_name == "trace") return Level::trace;
if (_name == "debug") return Level::debug;
if (_name == "warn") return Level::warn;
if (_name == "off") return Level::off;
return std::nullopt;
}

LoggerRegistry& LoggerRegistry::singleton()
{
static LoggerRegistry registry;
return registry;
}

LoggerRegistry::LoggerRegistry():
m_stream(&std::cerr)
{
}

LoggerRegistry::~LoggerRegistry() = default;

Logger const& LoggerRegistry::get(std::string_view _category)
{
std::string category{_category};
if (auto const loggerHandle = m_loggers.find(category); loggerHandle != m_loggers.end())
return loggerHandle->second->logger;

Level const effectiveLevel = computeEffectiveLevel(category);
auto [newLoggerHandle, _] = m_loggers.emplace(category, std::make_unique<LoggerHandle>(category, effectiveLevel));
return newLoggerHandle->second->logger;
}

void LoggerRegistry::setLevel(std::string_view _prefix, Level _level)
{
m_effectiveLevels[std::string(_prefix)] = _level;

for (auto& [category, loggerHandle]: m_loggers)
loggerHandle->effectiveLevel = computeEffectiveLevel(loggerHandle->category);
}

void LoggerRegistry::setOutput(std::ostream& _stream)
{
m_stream = &_stream;
}

void LoggerRegistry::reset()
{
m_effectiveLevels.clear();
for (auto& [category, loggerHandle]: m_loggers)
loggerHandle->effectiveLevel = Level::off;
m_stream = &std::cerr;
}

Level LoggerRegistry::computeEffectiveLevel(std::string_view _category) const
{
// Search for effective level, either defined for the category itself or for its closest ancestor
std::string key(_category);
while (true)
{
if (auto const it = m_effectiveLevels.find(key); it != m_effectiveLevels.end())
return it->second;

auto const dot = key.rfind('.');
if (dot == std::string::npos)
break;
key.resize(dot);
}

// Global level
if (auto const it = m_effectiveLevels.find(""); it != m_effectiveLevels.end())
return it->second;

return Level::off;
}

void LoggerRegistry::write(Level _level, std::string_view _category, std::string const& _message)
{
(*m_stream) << '[' << levelToString(_level) << ' ' << _category << "] " << _message << '\n';
}

void Logger::emitFormatted(Level _level, std::string _message) const
{
LoggerRegistry::singleton().write(_level, m_category, _message);
}
151 changes: 151 additions & 0 deletions libsolutil/Logger.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/*
This file is part of solidity.

solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0

/**
* Runtime switchable, hierarchical logging for the compiler.
* Loggers are named hierarchically with '.' separators, e.g. "yul.ssa.stacklayout", and are obtained lazily from LoggerRegistry.
* Every logger starts at Level::off unless there is a parent logger of different severity.
* Use DEFINE_LOGGER once and the solTrace/solDebug/solWarn macros at the call sites.
*/
#pragma once

#include <fmt/format.h>

#include <cstdint>
#include <map>
#include <memory>
#include <optional>
#include <ostream>
#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>

namespace solidity::log
{

/// Severity levels ordered from highest to lowest verbosity.
enum class Level: std::uint8_t
{
trace = 0,
debug = 1,
warn = 2,
off = 3
};

std::string_view levelToString(Level _level) noexcept;

std::optional<Level> levelFromString(std::string_view _name);

struct LoggerHandle;

class Logger
{
public:
bool shouldLog(Level _requested) const noexcept { return *m_level <= _requested; }

template<typename... Args>
void trace(fmt::format_string<Args...> _format, Args&&... _args) const
{
emit(Level::trace, _format, std::forward<Args>(_args)...);
}

template<typename... Args>
void debug(fmt::format_string<Args...> _format, Args&&... _args) const
{
emit(Level::debug, _format, std::forward<Args>(_args)...);
}

template<typename... Args>
void warn(fmt::format_string<Args...> _format, Args&&... _args) const
{
emit(Level::warn, _format, std::forward<Args>(_args)...);
}

private:
friend struct LoggerHandle;

Logger() noexcept = default;
Logger(Level const* _level, std::string_view _category) noexcept: m_level(_level), m_category(_category) {}

template<typename... Args>
void emit(Level _level, fmt::format_string<Args...> _format, Args&&... _args) const
{
emitFormatted(_level, fmt::format(_format, std::forward<Args>(_args)...));
}

void emitFormatted(Level _level, std::string _message) const;

/// Points to the effective level stored in the LoggerHandle cached by the registry
Level const* m_level = nullptr;
std::string_view m_category;
};

/// Process-wide singleton Registry of loggers
class LoggerRegistry
{
public:
static LoggerRegistry& singleton();

/// Returns the logger if it exists already.
/// Otherwise, creates it before returning.
Logger const& get(std::string_view _category);

/// Sets the level for all logs with _prefix.
void setLevel(std::string_view _prefix, Level _level);

void setOutput(std::ostream& _stream);

void reset();

private:
LoggerRegistry();
~LoggerRegistry();

/// Returns the level set for the category.
/// If there is no effective level set specifically for the category, searches for the closest
/// ancestor in the hierarchy which has a level set.
/// Returns Level::off in case nothing is found.
Level computeEffectiveLevel(std::string_view _category) const;

/// Writes a line prefixed with "[<level> <category>]"
void write(Level _level, std::string_view _category, std::string const& _message);

friend class Logger;

std::map<std::string, Level> m_effectiveLevels;
std::unordered_map<std::string, std::unique_ptr<LoggerHandle>> m_loggers;
std::ostream* m_stream;
};

}

/// Defines a file logger handle bound once to dottedCategory.
/// Subsequent uses are a single reference load.
#define DEFINE_LOGGER(variableName, dottedCategory) \
static ::solidity::log::Logger const& variableName = \
::solidity::log::LoggerRegistry::singleton().get(dottedCategory)

#define solLog(logger, lvl, ...) \
do { if ((logger).shouldLog(::solidity::log::Level::lvl)) [[unlikely]] \
(logger).lvl(__VA_ARGS__); \
} while (0)

#define solTrace(logger, ...) solLog((logger), trace, __VA_ARGS__)
#define solDebug(logger, ...) solLog((logger), debug, __VA_ARGS__)
#define solWarn(logger, ...) solLog((logger), warn, __VA_ARGS__)
18 changes: 18 additions & 0 deletions solc/CommandLineInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -824,9 +824,27 @@ bool CommandLineInterface::parseArguments(int _argc, char const* const* _argv)
}
m_options = parser.options();

applyLoggingOptions();

return true;
}

void CommandLineInterface::applyLoggingOptions()
{
auto& registry = log::LoggerRegistry::singleton();

// Pass false so that merely configuring logging does not mark the CLI as having produced output.
registry.setOutput(m_options.logging.toStdout ? sout(false) : serr(false));

// Apply the global default first, then the per-category overrides in order, so that the
// overrides supersede --log-level and later occurrences win.
if (m_options.logging.globalLevel)
registry.setLevel("", *m_options.logging.globalLevel);

for (auto const& [prefix, level]: m_options.logging.categoryLevels)
registry.setLevel(prefix, level);
}

void CommandLineInterface::processInput()
{
if (m_options.output.evmVersion < EVMVersion::constantinople())
Expand Down
3 changes: 3 additions & 0 deletions solc/CommandLineInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ class CommandLineInterface
std::optional<std::string> const& standardJsonInput() const { return m_standardJsonInput; }

private:
/// Applies the parsed logging options to the process-wide LoggerRegistry. Called once after
/// argument parsing, before any compilation work begins.
void applyLoggingOptions();
void printVersion();
void printLicense();
void compile();
Expand Down
Loading