blob: 07709dde7d3a9ff59f8e3c45f8a7a11375f50cbe [file] [log] [blame]
/**
* @file Logger.cpp
* Logger class implementation
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "core/logging/LoggerConfiguration.h"
#include <sys/stat.h>
#include <algorithm>
#include <atomic>
#include <map>
#include <memory>
#include <optional>
#include <queue>
#include <string>
#include <vector>
#include "core/Core.h"
#include "utils/StringUtils.h"
#include "utils/ClassUtils.h"
#include "utils/file/FileUtils.h"
#include "utils/Environment.h"
#include "core/logging/internal/LogCompressorSink.h"
#include "core/logging/alert/AlertSink.h"
#include "utils/Literals.h"
#include "core/TypedValues.h"
#include "core/logging/Utils.h"
#include "spdlog/spdlog.h"
#include "spdlog/sinks/stdout_sinks.h"
#include "spdlog/sinks/null_sink.h"
#ifdef WIN32
#include "core/logging/WindowsEventLogSink.h"
#else
#include "spdlog/sinks/syslog_sink.h"
#endif
#ifdef WIN32
#include <direct.h>
#define _WINSOCKAPI_
#include <windows.h>
#include <tchar.h>
#endif
namespace org::apache::nifi::minifi::core::logging {
const char* LoggerConfiguration::spdlog_default_pattern = "[%Y-%m-%d %H:%M:%S.%e] [%n] [%l] %v";
namespace internal {
void LoggerNamespace::forEachSink(const std::function<void(const std::shared_ptr<spdlog::sinks::sink>&)>& op) const {
for (auto& sink : sinks) {
op(sink);
}
for (auto& sink : exported_sinks) {
op(sink);
}
for (auto& [name, child] : children) {
child->forEachSink(op);
}
}
} // namespace internal
std::vector<std::string> LoggerProperties::get_keys_of_type(const std::string &type) {
std::vector<std::string> appenders;
std::string prefix = type + ".";
for (auto const & entry : getProperties()) {
if (entry.first.rfind(prefix, 0) == 0 && entry.first.find(".", prefix.length() + 1) == std::string::npos) {
appenders.push_back(entry.first);
}
}
return appenders;
}
LoggerConfiguration::LoggerConfiguration()
: root_namespace_(create_default_root()),
formatter_(std::make_shared<spdlog::pattern_formatter>(spdlog_default_pattern)) {
controller_ = std::make_shared<LoggerControl>();
logger_ = std::make_shared<LoggerImpl>(
core::getClassName<LoggerConfiguration>(),
std::nullopt,
controller_,
get_logger(nullptr, root_namespace_, core::getClassName<LoggerConfiguration>(), formatter_));
loggers.push_back(logger_);
}
LoggerConfiguration& LoggerConfiguration::getConfiguration() {
static LoggerConfiguration instance;
return instance;
}
void LoggerConfiguration::initialize(const std::shared_ptr<LoggerProperties> &logger_properties) {
std::lock_guard<std::mutex> lock(mutex);
root_namespace_ = initialize_namespaces(logger_properties, logger_);
alert_sinks_.clear();
root_namespace_->forEachSink([&] (const std::shared_ptr<spdlog::sinks::sink>& sink) {
if (auto alert_sink = std::dynamic_pointer_cast<AlertSink>(sink)) {
alert_sinks_.insert(std::move(alert_sink));
}
});
initializeCompression(lock, logger_properties);
std::string spdlog_pattern;
if (!logger_properties->getString("spdlog.pattern", spdlog_pattern)) {
spdlog_pattern = spdlog_default_pattern;
}
/**
* There is no need to shorten names per spdlog sink as this is a per log instance.
*/
if (const auto shorten_names_str = logger_properties->getString("spdlog.shorten_names")) {
shorten_names_ = utils::StringUtils::toBool(*shorten_names_str).value_or(false);
}
if (const auto include_uuid_str = logger_properties->getString("logger.include.uuid")) {
include_uuid_ = utils::StringUtils::toBool(*include_uuid_str).value_or(true);
}
formatter_ = std::make_shared<spdlog::pattern_formatter>(spdlog_pattern);
std::map<std::string, std::shared_ptr<spdlog::logger>> spdloggers;
for (auto const & logger_impl : loggers) {
std::shared_ptr<spdlog::logger> spdlogger;
auto it = spdloggers.find(logger_impl->name);
if (it == spdloggers.end()) {
spdlogger = get_logger(logger_, root_namespace_, logger_impl->name, formatter_, true);
spdloggers[logger_impl->name] = spdlogger;
} else {
spdlogger = it->second;
}
logger_impl->set_delegate(spdlogger);
}
logger_->log_debug("Set following pattern on loggers: %s", spdlog_pattern);
}
std::shared_ptr<Logger> LoggerConfiguration::getLogger(const std::string& name, const std::optional<utils::Identifier>& id) {
std::lock_guard<std::mutex> lock(mutex);
return getLogger(name, id, lock);
}
std::shared_ptr<Logger> LoggerConfiguration::getLogger(const std::string& name, const std::optional<utils::Identifier>& id, const std::lock_guard<std::mutex>& /*lock*/) {
std::string adjusted_name = name;
const std::string clazz = "class ";
auto haz_clazz = name.find(clazz);
if (haz_clazz == 0)
adjusted_name = name.substr(clazz.length(), name.length() - clazz.length());
if (shorten_names_) {
utils::ClassUtils::shortenClassName(adjusted_name, adjusted_name);
}
const auto id_if_enabled = include_uuid_ ? id : std::nullopt;
std::shared_ptr<LoggerImpl> result = std::make_shared<LoggerImpl>(adjusted_name, id_if_enabled, controller_, get_logger(logger_, root_namespace_, adjusted_name, formatter_));
loggers.push_back(result);
return result;
}
std::shared_ptr<spdlog::logger> LoggerConfiguration::getSpdlogLogger(const std::string& name) {
return spdlog::get(name);
}
std::shared_ptr<internal::LoggerNamespace> LoggerConfiguration::initialize_namespaces(const std::shared_ptr<LoggerProperties> &logger_properties, const std::shared_ptr<Logger> &logger) {
std::map<std::string, std::shared_ptr<spdlog::sinks::sink>> sink_map = logger_properties->initial_sinks();
std::string appender = "appender";
for (auto const & appender_key : logger_properties->get_keys_of_type(appender)) {
std::string appender_name = appender_key.substr(appender.length() + 1);
std::string appender_type;
if (!logger_properties->getString(appender_key, appender_type)) {
appender_type = "stderr";
}
std::transform(appender_type.begin(), appender_type.end(), appender_type.begin(), ::tolower);
if ("nullappender" == appender_type || "null appender" == appender_type || "null" == appender_type) {
sink_map[appender_name] = std::make_shared<spdlog::sinks::null_sink_st>();
} else if ("rollingappender" == appender_type || "rolling appender" == appender_type || "rolling" == appender_type) {
sink_map[appender_name] = getRotatingFileSink(appender_key, logger_properties);
} else if ("stdout" == appender_type) {
sink_map[appender_name] = std::make_shared<spdlog::sinks::stdout_sink_mt>();
} else if ("stderr" == appender_type) {
sink_map[appender_name] = std::make_shared<spdlog::sinks::stderr_sink_mt>();
} else if ("syslog" == appender_type) {
sink_map[appender_name] = LoggerConfiguration::create_syslog_sink();
} else if ("alert" == appender_type) {
if (auto sink = AlertSink::create(appender_key, logger_properties, logger)) {
sink_map[appender_name] = sink;
}
} else {
sink_map[appender_name] = LoggerConfiguration::create_fallback_sink();
}
}
std::shared_ptr<internal::LoggerNamespace> root_namespace = std::make_shared<internal::LoggerNamespace>();
std::string logger_type = "logger";
for (auto const & logger_key : logger_properties->get_keys_of_type(logger_type)) {
std::string logger_def;
if (!logger_properties->getString(logger_key, logger_def)) {
continue;
}
bool first = true;
spdlog::level::level_enum level = spdlog::level::info;
std::vector<std::shared_ptr<spdlog::sinks::sink>> sinks;
for (auto const & segment : utils::StringUtils::split(logger_def, ",")) {
std::string level_name = utils::StringUtils::trim(segment);
if (first) {
first = false;
auto opt_level = utils::parse_log_level(level_name);
if (opt_level) {
level = *opt_level;
}
} else {
if (auto it = sink_map.find(level_name); it != sink_map.end()) {
sinks.push_back(it->second);
} else {
logger->log_error("Couldn't find sink '%s'", level_name);
}
}
}
std::shared_ptr<internal::LoggerNamespace> current_namespace = root_namespace;
if (logger_key != "logger.root") {
for (auto const & name : utils::StringUtils::split(logger_key.substr(logger_type.length() + 1, logger_key.length() - logger_type.length()), "::")) {
auto child_pair = current_namespace->children.find(name);
std::shared_ptr<internal::LoggerNamespace> child;
if (child_pair == current_namespace->children.end()) {
child = std::make_shared<internal::LoggerNamespace>();
current_namespace->children[name] = child;
} else {
child = child_pair->second;
}
current_namespace = child;
}
}
current_namespace->level = level;
current_namespace->has_level = true;
current_namespace->sinks = sinks;
}
return root_namespace;
}
std::shared_ptr<spdlog::logger> LoggerConfiguration::get_logger(const std::shared_ptr<Logger>& logger, const std::shared_ptr<internal::LoggerNamespace> &root_namespace, const std::string &name,
const std::shared_ptr<spdlog::formatter>& formatter, bool remove_if_present) {
std::shared_ptr<spdlog::logger> spdlogger = spdlog::get(name);
if (spdlogger) {
if (remove_if_present) {
spdlog::drop(name);
} else {
return spdlogger;
}
}
std::shared_ptr<internal::LoggerNamespace> current_namespace = root_namespace;
std::vector<std::shared_ptr<spdlog::sinks::sink>> sinks = root_namespace->sinks;
std::vector<std::shared_ptr<spdlog::sinks::sink>> inherited_sinks;
spdlog::level::level_enum level = root_namespace->level;
std::string current_namespace_str;
std::string sink_namespace_str = "root";
std::string level_namespace_str = "root";
for (auto const & name_segment : utils::StringUtils::split(name, "::")) {
current_namespace_str += name_segment;
auto child_pair = current_namespace->children.find(name_segment);
if (child_pair == current_namespace->children.end()) {
break;
}
std::copy(current_namespace->exported_sinks.begin(), current_namespace->exported_sinks.end(), std::back_inserter(inherited_sinks));
current_namespace = child_pair->second;
if (!current_namespace->sinks.empty()) {
sinks = current_namespace->sinks;
sink_namespace_str = current_namespace_str;
}
if (current_namespace->has_level) {
level = current_namespace->level;
level_namespace_str = current_namespace_str;
}
current_namespace_str += "::";
}
if (logger != nullptr) {
const auto levelView(spdlog::level::to_string_view(level));
logger->log_debug("%s logger got sinks from namespace %s and level %s from namespace %s", name, sink_namespace_str, std::string(levelView.begin(), levelView.end()), level_namespace_str);
}
std::copy(inherited_sinks.begin(), inherited_sinks.end(), std::back_inserter(sinks));
spdlogger = std::make_shared<spdlog::logger>(name, begin(sinks), end(sinks));
spdlogger->set_level(level);
spdlogger->set_formatter(formatter->clone());
spdlogger->flush_on(std::max(spdlog::level::info, current_namespace->level));
try {
spdlog::register_logger(spdlogger);
} catch (const spdlog::spdlog_ex &) {
// Ignore as someone else beat us to registration, we should get the one they made below
}
return spdlog::get(name);
}
spdlog::sink_ptr LoggerConfiguration::create_syslog_sink() {
#ifdef WIN32
return std::make_shared<internal::windowseventlog_sink>("ApacheNiFiMiNiFi");
#else
return std::dynamic_pointer_cast<spdlog::sinks::sink>(spdlog::syslog_logger_mt("ApacheNiFiMiNiFi", "", 0, LOG_USER, false));
#endif
}
spdlog::sink_ptr LoggerConfiguration::create_fallback_sink() {
if (utils::Environment::isRunningAsService()) {
return LoggerConfiguration::create_syslog_sink();
} else {
return std::dynamic_pointer_cast<spdlog::sinks::sink>(std::make_shared<spdlog::sinks::stderr_sink_mt>());
}
}
std::shared_ptr<internal::LoggerNamespace> LoggerConfiguration::create_default_root() {
std::shared_ptr<internal::LoggerNamespace> result = std::make_shared<internal::LoggerNamespace>();
result->sinks = { std::make_shared<spdlog::sinks::stderr_sink_mt>() };
result->level = spdlog::level::info;
return result;
}
void LoggerConfiguration::initializeCompression(const std::lock_guard<std::mutex>& lock, const std::shared_ptr<LoggerProperties>& properties) {
auto compression_sink = compression_manager_.initialize(properties, logger_, [&] (const std::string& name) {return getLogger(name, std::nullopt, lock);});
if (compression_sink) {
root_namespace_->sinks.push_back(compression_sink);
root_namespace_->exported_sinks.push_back(compression_sink);
}
}
void LoggerConfiguration::initializeAlertSinks(core::controller::ControllerServiceProvider* controller, const std::shared_ptr<AgentIdentificationProvider>& agent_id) {
std::lock_guard guard(mutex);
for (auto& sink : alert_sinks_) {
sink->initialize(controller, agent_id);
}
}
std::shared_ptr<spdlog::sinks::rotating_file_sink_mt> LoggerConfiguration::getRotatingFileSink(const std::string& appender_key, const std::shared_ptr<LoggerProperties>& properties) {
// According to spdlog docs, if two loggers write to the same file, they must use the same sink object.
// Note that some logging configuration changes will not take effect until MiNiFi is restarted.
static std::map<std::filesystem::path, std::shared_ptr<spdlog::sinks::rotating_file_sink_mt>> rotating_file_sinks;
static std::mutex sink_map_mtx;
std::string file_name_str;
if (!properties->getString(appender_key + ".file_name", file_name_str)) {
file_name_str = "minifi-app.log";
}
std::string directory_str;
if (!properties->getString(appender_key + ".directory", directory_str)) {
// The below part assumes logger_properties->getHome() is existing
// Cause minifiHome must be set at MiNiFiMain.cpp?
directory_str = (properties->getHome() / "logs").string();
}
auto file_name = std::filesystem::path(directory_str) / file_name_str;
if (utils::file::FileUtils::create_dir(directory_str) == -1) {
std::cerr << directory_str << " cannot be created\n";
exit(1);
}
int max_files = 3;
std::string max_files_str;
if (properties->getString(appender_key + ".max_files", max_files_str)) {
try {
max_files = std::stoi(max_files_str);
} catch (const std::invalid_argument &) {
} catch (const std::out_of_range &) {
}
}
int max_file_size = 5_MiB;
std::string max_file_size_str;
if (properties->getString(appender_key + ".max_file_size", max_file_size_str)) {
try {
max_file_size = std::stoi(max_file_size_str);
} catch (const std::invalid_argument &) {
} catch (const std::out_of_range &) {
}
}
std::lock_guard<std::mutex> guard(sink_map_mtx);
auto it = rotating_file_sinks.find(file_name);
if (it != rotating_file_sinks.end()) {
return it->second;
}
auto sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(file_name.string(), max_file_size, max_files);
rotating_file_sinks.emplace(file_name, sink);
return sink;
}
} // namespace org::apache::nifi::minifi::core::logging