Skip to content

Instantly share code, notes, and snippets.

@kinchungwong
Last active January 26, 2019 18:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kinchungwong/83a7f64193ba915fb9b9096255e2befc to your computer and use it in GitHub Desktop.
Save kinchungwong/83a7f64193ba915fb9b9096255e2befc to your computer and use it in GitHub Desktop.

About this prototype code

This prototype code illustrates the changes needed to support string prefix in LogLevelManager, compared to the earlier code in the previous Gist which only supports exact string matching.


What is enabled by this code change

In the previous prototype, LogLevelManager allows setting log level by name. The name has to match exactly, including case sensitivity.

In this prototype, LogLevelManager allows setting any number of log levels that has a specified prefix: setPrefixLogLevel("img", 4) will change the log level "imgproc" and "imgcodecs" to 4, along with any other log variable names that start with "img".


Reminder

This implementation is quite error-prone. There are also plenty of "astonishments" ( a violation of POLA) in this implementation. Refer to code comment for details.

Some of these "astonishments" cannot be fixed, unless a rigid "precedence order of application of rules" is defined and imposed, and then the implementation can be verified to satisfy the specification.

Copyright 2019 Ryan Wong
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#pragma once
namespace logging_20190125
{
// A registry for log filtering thresholds.
//
// Two kinds of things are tracked:
// - log level variables
// - changes to log level values
//
// A log level variable is a piece of memory containing an integer value.
// Once registered, any change to the log level will be written directly to that
// memory.
//
// Application code which knows of a particular log level variable can read
// from it directly, without going through LogLevelManager. This happens when
// the log message macro is invoked.
//
// Changes to log level values can be made anytime. This class is responsible for
// pushing the change into the log level variable, if the latter is registered.
// If the latter is not yet registered, this class remembers the change, so that
// it can be pushed later.
//
// There is limited name prefix support, allowing wildcard suffix of this form:
// "tag_name_*". The support is limited to pushing a log level value to all
// log level variables matching the prefix for just once.
// See setPrefixLogLevel().
//
class LogLevelManager_2
{
private:
LogLevelManager_2();
~LogLevelManager_2();
public:
static LogLevelManager_2& getInstance();
public:
// Associates a certain tag name with the address of a log level variable.
//
// Requirement: this log level variable need to have static (indefinite) lifetime.
// If the variable becomes invalid, undefined behavior (UB) can happen, including
// crash, memory corruption, or non-obvious abnormal behaviors.
//
void registerLogLevel(const std::string& name, int& refLogLevel);
// Configures a log level threshold value for the specified tag name.
//
void setLogLevel(const std::string& name, int logLevel);
// Gets the log level.
// If a variable has been registered, the variable's current value will be used.
// If a variable hasn't been registered, the stored configured value will be used.
//
// Any application code that has direct access to the log level variable may
// directly read or write that variable, without going through LogLevelManager.
//
int getLogLevel(const std::string& name) const;
// Applies a one-time log level threshold value to all tag names having
// the specified prefix.
//
// Do not put any wildcard characters into the the string "namePrefix".
// This function does not actually parse the string; it merely searches
// for tag names having that prefix.
//
// Any tag names matching that prefix that haven't been registered yet
// will receive this log level when they are first registered.
//
// Any tag names matching that prefix that's already registered will
// have their log levels changed to the specified value, as part of this
// function call.
//
// For each tag name matching the specified prefix, the log level will
// only be applied once, either during this call or during a forthcoming
// registration.
//
// IMPORTANT
// If a log level variable is to be registered in the future, and at the
// time of its registration, more than one prefix is found to be applicable,
// the order (precedence) in which they are applied is unspecified.
//
void setPrefixLogLevel(const std::string& prefix, int logLevel);
private:
using MutexType = std::recursive_mutex;
using LockType = std::lock_guard<MutexType>;
private:
void internal_applyConfiguredLogLevel_onRegistering(const LockType& lock, const std::pair<const std::string, int*>& registeredPair);
void internal_applyPrefixLogLevel_onPrefixAdded(const LockType& lock, const std::pair<const std::string, int>& prefixPair);
void internal_applyPrefixLogLevel_onRegistering(const LockType& lock, const std::pair<const std::string, int*>& registeredPair);
private:
mutable MutexType m_mutex;
std::unordered_map<std::string, int> m_configuredLogLevels;
std::unordered_map<std::string, int*> m_registeredLogLevels;
std::map<std::string, int> m_prefixLogLevels;
};
}
#include "pch.h"
#include "LogLevelManager_PrefixSupport.h"
namespace logging_20190125
{
LogLevelManager_2::LogLevelManager_2()
: m_mutex()
, m_configuredLogLevels()
, m_registeredLogLevels()
, m_prefixLogLevels()
{
}
LogLevelManager_2::~LogLevelManager_2()
{
}
LogLevelManager_2& LogLevelManager_2::getInstance()
{
static LogLevelManager_2 instance{};
return instance;
}
void LogLevelManager_2::registerLogLevel(const std::string& name, int& refLogLevel)
{
LockType lock(m_mutex);
auto regIter = m_registeredLogLevels.find(name);
if (regIter == m_registeredLogLevels.end())
{
regIter = m_registeredLogLevels.emplace(name, std::addressof(refLogLevel)).first;
}
internal_applyPrefixLogLevel_onRegistering(lock, *regIter);
internal_applyConfiguredLogLevel_onRegistering(lock, *regIter);
}
void LogLevelManager_2::setLogLevel(const std::string& name, int logLevel)
{
LockType lock(m_mutex);
auto cfgIter = m_configuredLogLevels.find(name);
if (cfgIter == m_configuredLogLevels.end())
{
cfgIter = m_configuredLogLevels.emplace(name, logLevel).first;
}
else
{
cfgIter->second = logLevel;
}
auto regIter = m_registeredLogLevels.find(name);
if (regIter != m_registeredLogLevels.end())
{
*(regIter->second) = (cfgIter->second);
}
}
int LogLevelManager_2::getLogLevel(const std::string& name) const
{
LockType lock(m_mutex);
const auto regIter = m_registeredLogLevels.find(name);
if (regIter != m_registeredLogLevels.end())
{
return *(regIter->second);
}
const auto cfgIter = m_configuredLogLevels.find(name);
if (cfgIter != m_configuredLogLevels.end())
{
return (cfgIter->second);
}
// ====== TODO ======
return -1; // or something
}
void LogLevelManager_2::setPrefixLogLevel(const std::string& prefix, int logLevel)
{
LockType lock(m_mutex);
auto prefixIter = m_prefixLogLevels.find(prefix);
if (prefixIter == m_prefixLogLevels.end())
{
prefixIter = m_prefixLogLevels.emplace(prefix, logLevel).first;
}
else
{
prefixIter->second = logLevel;
}
internal_applyPrefixLogLevel_onPrefixAdded(lock, *prefixIter);
}
void LogLevelManager_2::internal_applyConfiguredLogLevel_onRegistering(const LockType& lock,
const std::pair<const std::string, int*>& registeredPair)
{
const std::string& name = registeredPair.first;
auto cfgIter = m_configuredLogLevels.find(name);
if (cfgIter != m_configuredLogLevels.end())
{
*(registeredPair.second) = (cfgIter->second);
}
}
void LogLevelManager_2::internal_applyPrefixLogLevel_onPrefixAdded(const LockType& lock,
const std::pair<const std::string, int>& prefixPair)
{
const std::string& prefix = prefixPair.first;
const size_t prefixLen = prefix.length();
for (auto& registeredPair : m_registeredLogLevels)
{
const std::string& name = registeredPair.first;
const size_t nameLen = name.length();
if (nameLen < prefixLen)
{
continue;
}
// having checked string length, memcmp can be used
// since we don't anticipate null terminating chars inside strings.
if (0 != memcmp(prefix.c_str(), name.c_str(), prefixLen))
{
continue;
}
*(registeredPair.second) = prefixPair.second;
}
}
void LogLevelManager_2::internal_applyPrefixLogLevel_onRegistering(const LockType& lock,
const std::pair<const std::string, int*>& registeredPair)
{
using PrefixIterType = decltype(m_prefixLogLevels)::const_iterator;
constexpr size_t prefixSearchStrategyThreshold = 10u;
const size_t prefixListCount = m_prefixLogLevels.size();
const std::string& name = registeredPair.first;
const size_t nameLen = name.length();
PrefixIterType prefixIterBegin;
PrefixIterType prefixIterEnd;
if (prefixListCount < prefixSearchStrategyThreshold)
{
// If list is short, iterate the whole list
prefixIterBegin = m_prefixLogLevels.cbegin();
prefixIterEnd = m_prefixLogLevels.cend();
}
else
{
// If list is long, use bounded search
//
// REMARK
// When co-opting sorted list for string prefix search,
// the lower-bound and upper-bound logic is not intuitive.
// There may be errors, but there are also non-errors -
// code that "looks like wrong" but is actually correct.
//
// TODO
// Need formally prove correctness, or else replace with a
// provably correct implementation.
//
// REMARK (performance)
// Using a sorted list for string prefix search is actually
// a poor choice. A binary search tree is not a prefix tree.
//
const char nameFirstChar = name.front();
const std::string nameLowerBound{ 1u, nameFirstChar };
const std::string nameUpperBound{ 1u, std::min<char>(nameFirstChar, (char)127) };
prefixIterBegin = m_prefixLogLevels.lower_bound(nameLowerBound);
prefixIterEnd = m_prefixLogLevels.lower_bound(nameUpperBound);
}
for (PrefixIterType prefixIter = prefixIterBegin;
prefixIter != prefixIterEnd;
++prefixIter)
{
const auto& prefixPair = *prefixIter;
const std::string& prefix = prefixPair.first;
const size_t prefixLen = prefix.length();
if (nameLen < prefixLen)
{
continue;
}
// having checked string length, memcmp can be used
// since we don't anticipate null terminating chars inside strings.
if (0 != memcmp(prefix.c_str(), name.c_str(), prefixLen))
{
continue;
}
*(registeredPair.second) = prefixPair.second;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment