core/logger/Logger.cpp (339 lines of code) (raw):
// Copyright 2022 iLogtail Authors
//
// Licensed 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 "Logger.h"
#include <set>
#include "boost/filesystem.hpp"
#include "json/json.h"
#include "spdlog/async.h"
#include "spdlog/sinks/rotating_file_sink.h"
#include "spdlog/sinks/stdout_sinks.h"
#include "app_config/AppConfig.h"
#include "common/ErrorUtil.h"
#include "common/ExceptionBase.h"
#include "common/FileSystemUtil.h"
#include "common/Flags.h"
#include "common/RuntimeUtil.h"
#include "common/StringTools.h"
DEFINE_FLAG_BOOL(logtail_async_logger_enable, "", true);
DEFINE_FLAG_INT32(logtail_async_logger_queue_size, "", 1024);
DEFINE_FLAG_INT32(logtail_async_logger_thread_num, "", 1);
// Global loggers, set in InitGlobalLoggers.
logtail::Logger::logger sLogger;
namespace logtail {
namespace level = spdlog::level;
// TODO: There is a CALL level in apsara, have to mapping it??
static bool MapStringToLevel(const std::string& str, level::level_enum& level) {
if ("TRACE" == str)
level = level::trace;
else if ("DEBUG" == str)
level = level::debug;
else if ("INFO" == str)
level = level::info;
else if ("WARNING" == str)
level = level::warn;
else if ("ERROR" == str)
level = level::err;
else if ("FATAL" == str)
level = level::critical;
else
return false;
return true;
}
static std::string MapLevelToString(level::level_enum level) {
switch (level) {
case level::trace:
return "TRACE";
case level::debug:
return "DEBUG";
case level::info:
return "INFO";
case level::warn:
return "WARNING";
case level::err:
return "ERROR";
case level::critical:
return "FATAL";
default:
return "INFO";
}
}
Logger& Logger::Instance() {
// Works fine after C++11.
static Logger logger;
return logger;
}
Logger::Logger() {
if (BOOL_FLAG(logtail_async_logger_enable)) {
spdlog::init_thread_pool(INT32_FLAG(logtail_async_logger_queue_size),
INT32_FLAG(logtail_async_logger_thread_num));
}
mInnerLogger.open(GetAgentLogDir() + "logger_initialization.log");
LoadConfig(GetAgentConfDir() + "apsara_log_conf.json");
mInnerLogger.close();
}
Logger::~Logger() {
}
void Logger::LogMsg(const std::string& msg) {
if (!mInnerLogger)
return;
mInnerLogger << msg << std::endl;
}
void Logger::InitGlobalLoggers() {
if (!sLogger) {
sLogger = GetLogger(GetAgentLoggersPrefix());
}
}
Logger::logger Logger::CreateLogger(const std::string& loggerName,
const std::string& logFilePath,
unsigned int maxLogFileNum,
unsigned long long maxLogFileSize,
unsigned int maxDaysFromModify, // Not supported
const std::string compress) // Not supported
{
auto absoluteFilePath = logFilePath;
#if defined(_MSC_VER)
if (std::string::npos == absoluteFilePath.find(":"))
#elif defined(__linux__)
if (!absoluteFilePath.empty() && absoluteFilePath[0] != '/')
#endif
{
absoluteFilePath = GetAgentLogDir() + absoluteFilePath;
}
LogMsg("Path of logger named " + loggerName + ": " + absoluteFilePath);
// TODO: Add supports for @maxDaysFromModify and @compress.
using rotating_sink = spdlog::sinks::rotating_file_sink_mt;
static std::map<std::string, std::shared_ptr<rotating_sink>> sinksCache;
try {
auto cacheIter = sinksCache.find(absoluteFilePath);
std::shared_ptr<rotating_sink> sink = nullptr;
if (sinksCache.end() == cacheIter) {
sink = std::make_shared<rotating_sink>(absoluteFilePath, maxLogFileSize, maxLogFileNum);
if (nullptr == sink) {
LogMsg(std::string("Create sink failed, params: ") + absoluteFilePath + ", "
+ std::to_string(maxLogFileSize) + std::to_string(maxLogFileNum));
return nullptr;
}
sinksCache.insert(std::make_pair(absoluteFilePath, sink));
} else
sink = cacheIter->second;
if (BOOL_FLAG(logtail_async_logger_enable)) {
return std::make_shared<spdlog::async_logger>(
loggerName, sink, spdlog::thread_pool(), spdlog::async_overflow_policy::overrun_oldest);
} else {
return std::make_shared<spdlog::logger>(loggerName, sink);
}
} catch (std::exception& e) {
LogMsg(std::string("CreateLogger exception, logger name: ") + loggerName + ", exception: " + e.what());
} catch (...) {
LogMsg(std::string("CreateLogger exception, logger name: ") + loggerName + ", exception: unknown");
}
return nullptr;
}
Logger::logger Logger::GetLogger(const std::string& loggerName) {
auto logger = spdlog::get(loggerName);
return (logger != nullptr) ? logger : spdlog::get(DEFAULT_LOGGER_NAME);
}
// Config file schema.
// "Loggers": {
// "/": { // Key to get the logger, the default logger.
// // Attributes for the logger, (sink_name, log_level).
// "AsyncFileSink": "WARNING"
// },
// "/apsara/loongcollector": { // Another logger.
// // ....
// }
// },
// "Sinks": {
// ”AsyncFileSink": { // Sink name
// // Attributes for the sink
// },
// "AsyncFileSinkProfile": { // Another sink
// }
// }
// If no default logger is provided, we will create one (with default sink if it is not exist too).
// If the sink of a logger is not exist, it will not be created.
// If sink is defined, all parameters must be provided except for Compress (empty by default).
void Logger::LoadConfig(const std::string& filePath) {
std::map<std::string, LoggerConfig> loggerConfigs;
std::map<std::string, SinkConfig> sinkConfigs;
std::string logConfigInfo;
// Load config file, check if it is valid or not.
do {
std::ifstream in(filePath);
if (!in)
break;
in.seekg(0, std::ios::end);
size_t len = in.tellg();
in.seekg(0, std::ios::beg);
std::vector<char> buffer(len + 1, '\0');
in.read(buffer.data(), len);
in.close();
Json::Value jsonRoot;
Json::CharReaderBuilder builder;
builder["collectComments"] = false;
std::unique_ptr<Json::CharReader> jsonReader(builder.newCharReader());
std::string jsonParseErrs;
if (!jsonReader->parse(buffer.data(), buffer.data() + buffer.size(), &jsonRoot, &jsonParseErrs)) {
break;
}
// Parse Sinks at first, so that we can find them when parsing Loggers.
if (!jsonRoot.isMember("Sinks") || !jsonRoot["Sinks"].isObject() || jsonRoot["Sinks"].empty()) {
break;
}
auto& jsonSinks = jsonRoot["Sinks"];
auto sinkNames = jsonSinks.getMemberNames();
for (auto& name : sinkNames) {
auto& jsonSink = jsonSinks[name];
if (!jsonSink.isObject()
|| !(jsonSink.isMember("Type") && jsonSink["Type"].isString() && !jsonSink["Type"].asString().empty())
|| !(jsonSink.isMember("MaxLogFileNum") && jsonSink["MaxLogFileNum"].isIntegral()
&& jsonSink["MaxLogFileNum"].asInt() > 0)
|| !(jsonSink.isMember("MaxLogFileSize") && jsonSink["MaxLogFileSize"].isIntegral()
&& jsonSink["MaxLogFileSize"].asInt64() > 0)
|| !(jsonSink.isMember("MaxDaysFromModify") && jsonSink["MaxDaysFromModify"].isIntegral()
&& jsonSink["MaxDaysFromModify"].asInt() > 0)
|| !(jsonSink.isMember("LogFilePath") && jsonSink["LogFilePath"].isString()
&& !jsonSink["LogFilePath"].asString().empty())) {
continue;
}
SinkConfig sinkCfg;
sinkCfg.type = jsonSink["Type"].asString();
sinkCfg.maxLogFileNum = jsonSink["MaxLogFileNum"].asInt();
sinkCfg.maxLogFileSize = jsonSink["MaxLogFileSize"].asInt64();
sinkCfg.maxDaysFromModify = jsonSink["MaxDaysFromModify"].asInt();
sinkCfg.logFilePath = jsonSink["LogFilePath"].asString();
if (jsonSink.isMember("Compress") && jsonSink["Compress"].isString()) {
sinkCfg.compress = jsonSink["Compress"].asString();
}
sinkConfigs[name] = std::move(sinkCfg);
}
if (sinkConfigs.empty())
break;
if (!jsonRoot.isMember("Loggers") || !jsonRoot["Loggers"].isObject() || jsonRoot["Loggers"].empty()) {
break;
}
auto& jsonLoggers = jsonRoot["Loggers"];
auto loggerNames = jsonLoggers.getMemberNames();
for (auto& name : loggerNames) {
auto& jsonLogger = jsonLoggers[name];
if (!jsonLogger.isObject() || jsonLogger.empty())
continue;
auto sinkName = jsonLogger.getMemberNames()[0];
if (sinkName.empty() || !jsonLogger[sinkName].isString())
continue;
if (sinkConfigs.find(sinkName) == sinkConfigs.end())
continue;
LoggerConfig logCfg;
if (!MapStringToLevel(jsonLogger[sinkName].asString(), logCfg.level))
continue;
logCfg.sinkName = std::move(sinkName);
loggerConfigs[name] = std::move(logCfg);
}
logConfigInfo = "Load log config from " + filePath;
} while (0);
// parse env log level
level::level_enum envLogLevel = level::info;
std::string aliyun_logtail_log_level;
const char* envLogLevelVal = std::getenv("LOGTAIL_LOG_LEVEL");
if (envLogLevelVal) {
aliyun_logtail_log_level = envLogLevelVal;
}
if (MapStringToLevel(ToUpperCaseString(aliyun_logtail_log_level), envLogLevel)) {
LogMsg(std::string("Load log level from the env success, level: ") + aliyun_logtail_log_level);
} else {
LogMsg(std::string("Load log level from the env error, level: ") + aliyun_logtail_log_level);
aliyun_logtail_log_level = "";
}
// Add or supply default config(s).
bool needSave = true;
if (loggerConfigs.empty()) {
logConfigInfo = "Load log config file failed, use default configs.";
LoadAllDefaultConfigs(loggerConfigs, sinkConfigs);
} else if (loggerConfigs.end() == loggerConfigs.find(DEFAULT_LOGGER_NAME)) {
logConfigInfo += ", and add default config.";
LoadDefaultConfig(loggerConfigs, sinkConfigs);
} else
needSave = false;
EnsureSnapshotDirExist(sinkConfigs);
LogMsg(logConfigInfo);
LogMsg(std::string("Logger size in config: ") + std::to_string(loggerConfigs.size()));
// Create loggers, record failed loggers.
std::set<std::string> failedLoggers;
for (auto& loggerIter : loggerConfigs) {
auto& name = loggerIter.first;
auto& loggerCfg = loggerIter.second;
auto& sinkCfg = sinkConfigs[loggerCfg.sinkName];
auto logger = CreateLogger(name,
sinkCfg.logFilePath,
sinkCfg.maxLogFileNum,
sinkCfg.maxLogFileSize,
sinkCfg.maxDaysFromModify,
sinkCfg.compress);
if (nullptr == logger) {
failedLoggers.insert(name);
LogMsg(std::string("[ERROR] logger named ") + name + " created failed.");
continue;
}
spdlog::register_logger(logger);
logger->set_pattern(DEFAULT_PATTERN);
if (name == GetAgentLoggersPrefix() && !aliyun_logtail_log_level.empty()) {
logger->set_level(envLogLevel);
logger->flush_on(envLogLevel);
} else {
logger->set_level(loggerCfg.level);
logger->flush_on(loggerCfg.level);
}
LogMsg(std::string("logger named ") + name + " created.");
}
if (failedLoggers.empty()) {
if (needSave)
SaveConfig(filePath, loggerConfigs, sinkConfigs);
return;
}
// If the default logger failed, downgrade to console logger.
// It is useless, but we need to offer at least one available logger.
if (failedLoggers.find(DEFAULT_LOGGER_NAME) != failedLoggers.end()) {
try {
spdlog::stdout_logger_mt(DEFAULT_LOGGER_NAME);
} catch (...) {
LogMsg("Create console logger for default logger failed, we have no idea now.");
throw ExceptionBase("Initialize logger failed......");
}
failedLoggers.erase(DEFAULT_LOGGER_NAME);
}
}
void Logger::SaveConfig(const std::string& filePath,
std::map<std::string, LoggerConfig>& loggerCfgs,
std::map<std::string, SinkConfig>& sinkCfgs) {
std::ofstream out(filePath);
if (!out) {
LogMsg("SaveConfig open file " + filePath + " failed");
return;
}
Json::Value rootVal(Json::objectValue);
// Loggers.
Json::Value loggerVal(Json::objectValue);
for (auto& lgIter : loggerCfgs) {
auto& name = lgIter.first;
auto& loggerCfg = lgIter.second;
Json::Value lgValue(Json::objectValue);
lgValue[loggerCfg.sinkName] = MapLevelToString(loggerCfg.level);
loggerVal[name] = lgValue;
}
rootVal["Loggers"] = loggerVal;
// Sinks.
Json::Value sinkVal(Json::objectValue);
for (auto& sinkIter : sinkCfgs) {
auto& name = sinkIter.first;
auto& sinkCfg = sinkIter.second;
Json::Value skValue(Json::objectValue);
skValue["Type"] = sinkCfg.type;
skValue["MaxLogFileNum"] = sinkCfg.maxLogFileNum;
skValue["MaxLogFileSize"] = Json::UInt64(sinkCfg.maxLogFileSize);
skValue["MaxDaysFromModify"] = sinkCfg.maxDaysFromModify;
skValue["LogFilePath"] = sinkCfg.logFilePath;
skValue["Compress"] = sinkCfg.compress;
sinkVal[name] = skValue;
}
rootVal["Sinks"] = sinkVal;
Json::StreamWriterBuilder builder;
builder["indentation"] = "\t";
out << Json::writeString(builder, rootVal);
}
void Logger::LoadDefaultConfig(std::map<std::string, LoggerConfig>& loggerCfgs,
std::map<std::string, SinkConfig>& sinkCfgs) {
loggerCfgs.insert({DEFAULT_LOGGER_NAME, LoggerConfig{"AsyncFileSink", level::warn}});
if (sinkCfgs.find("AsyncFileSink") != sinkCfgs.end())
return;
sinkCfgs.insert(
{"AsyncFileSink", SinkConfig{"AsyncFile", 10, 20000000, 300, GetAgentLogDir() + GetAgentLogName(), "Gzip"}});
}
void Logger::LoadAllDefaultConfigs(std::map<std::string, LoggerConfig>& loggerCfgs,
std::map<std::string, SinkConfig>& sinkCfgs) {
LoadDefaultConfig(loggerCfgs, sinkCfgs);
loggerCfgs.insert({GetAgentLoggersPrefix(), LoggerConfig{"AsyncFileSink", level::info}});
}
void Logger::EnsureSnapshotDirExist(std::map<std::string, SinkConfig>& sinkCfgs) {
if (sinkCfgs.size() == 0) {
return;
}
for (const auto& sink : sinkCfgs) {
std::string sinkName = sink.first;
SinkConfig sinkCfg = sink.second;
if (sinkName == "AsyncFileSinkStatus" || sinkName == "AsyncFileSinkProfile") {
boost::filesystem::path logFilePath(sinkCfg.logFilePath);
try {
boost::filesystem::create_directories(logFilePath.parent_path());
} catch (const boost::filesystem::filesystem_error& e) {
LogMsg(std::string("Create snapshot dir error ") + logFilePath.parent_path().string() + ", error"
+ std::string(e.what()));
}
}
}
}
} // namespace logtail