Older/ToolKit/Util/logger.cpp
amass 9de3af15eb
All checks were successful
Deploy / PullDocker (push) Successful in 12s
Deploy / Build (push) Successful in 1m51s
add ZLMediaKit code for learning.
2024-09-28 23:55:00 +08:00

655 lines
18 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved.
*
* This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit).
*
* Use of this source code is governed by MIT license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include <sys/stat.h>
#include <cstdarg>
#include <iostream>
#include "logger.h"
#include "onceToken.h"
#include "File.h"
#include "NoticeCenter.h"
#if defined(_WIN32)
#include "strptime_win.h"
#endif
#ifdef ANDROID
#include <android/log.h>
#endif //ANDROID
#if defined(__MACH__) || ((defined(__linux) || defined(__linux__)) && !defined(ANDROID))
#include <sys/syslog.h>
#endif
using namespace std;
namespace toolkit {
#ifdef _WIN32
#define CLEAR_COLOR 7
static const WORD LOG_CONST_TABLE[][3] = {
{0x97, 0x09 , 'T'},//蓝底灰字黑底蓝字window console默认黑底
{0xA7, 0x0A , 'D'},//绿底灰字,黑底绿字
{0xB7, 0x0B , 'I'},//天蓝底灰字,黑底天蓝字
{0xE7, 0x0E , 'W'},//黄底灰字,黑底黄字
{0xC7, 0x0C , 'E'} };//红底灰字,黑底红字
bool SetConsoleColor(WORD Color)
{
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
if (handle == 0)
return false;
BOOL ret = SetConsoleTextAttribute(handle, Color);
return(ret == TRUE);
}
#else
#define CLEAR_COLOR "\033[0m"
static const char *LOG_CONST_TABLE[][3] = {
{"\033[44;37m", "\033[34m", "T"},
{"\033[42;37m", "\033[32m", "D"},
{"\033[46;37m", "\033[36m", "I"},
{"\033[43;37m", "\033[33m", "W"},
{"\033[41;37m", "\033[31m", "E"}};
#endif
Logger *g_defaultLogger = nullptr;
Logger &getLogger() {
if (!g_defaultLogger) {
g_defaultLogger = &Logger::Instance();
}
return *g_defaultLogger;
}
void setLogger(Logger *logger) {
g_defaultLogger = logger;
}
///////////////////Logger///////////////////
INSTANCE_IMP(Logger, exeName())
Logger::Logger(const string &loggerName) {
_logger_name = loggerName;
_last_log = std::make_shared<LogContext>();
_default_channel = std::make_shared<ConsoleChannel>("default", LTrace);
#if defined(_WIN32)
SetConsoleOutputCP(CP_UTF8);
#endif
}
Logger::~Logger() {
_writer.reset();
{
LogContextCapture(*this, LInfo, __FILE__, __FUNCTION__, __LINE__);
}
_channels.clear();
}
void Logger::add(const std::shared_ptr<LogChannel> &channel) {
_channels[channel->name()] = channel;
}
void Logger::del(const string &name) {
_channels.erase(name);
}
std::shared_ptr<LogChannel> Logger::get(const string &name) {
auto it = _channels.find(name);
if (it == _channels.end()) {
return nullptr;
}
return it->second;
}
void Logger::setWriter(const std::shared_ptr<LogWriter> &writer) {
_writer = writer;
}
void Logger::write(const LogContextPtr &ctx) {
if (_writer) {
_writer->write(ctx, *this);
} else {
writeChannels(ctx);
}
}
void Logger::setLevel(LogLevel level) {
for (auto &chn : _channels) {
chn.second->setLevel(level);
}
}
void Logger::writeChannels_l(const LogContextPtr &ctx) {
if (_channels.empty()) {
_default_channel->write(*this, ctx);
} else {
for (auto &chn : _channels) {
chn.second->write(*this, ctx);
}
}
_last_log = ctx;
_last_log->_repeat = 0;
}
//返回毫秒
static int64_t timevalDiff(struct timeval &a, struct timeval &b) {
return (1000 * (b.tv_sec - a.tv_sec)) + ((b.tv_usec - a.tv_usec) / 1000);
}
void Logger::writeChannels(const LogContextPtr &ctx) {
if (ctx->_line == _last_log->_line && ctx->_file == _last_log->_file && ctx->str() == _last_log->str() && ctx->_thread_name == _last_log->_thread_name) {
//重复的日志每隔500ms打印一次过滤频繁的重复日志
++_last_log->_repeat;
if (timevalDiff(_last_log->_tv, ctx->_tv) > 500) {
ctx->_repeat = _last_log->_repeat;
writeChannels_l(ctx);
}
return;
}
if (_last_log->_repeat) {
writeChannels_l(_last_log);
}
writeChannels_l(ctx);
}
const string &Logger::getName() const {
return _logger_name;
}
///////////////////LogContext///////////////////
static inline const char *getFileName(const char *file) {
auto pos = strrchr(file, '/');
#ifdef _WIN32
if(!pos){
pos = strrchr(file, '\\');
}
#endif
return pos ? pos + 1 : file;
}
static inline const char *getFunctionName(const char *func) {
#ifndef _WIN32
return func;
#else
auto pos = strrchr(func, ':');
return pos ? pos + 1 : func;
#endif
}
LogContext::LogContext(LogLevel level, const char *file, const char *function, int line, const char *module_name, const char *flag)
: _level(level), _line(line), _file(getFileName(file)), _function(getFunctionName(function)),
_module_name(module_name), _flag(flag) {
gettimeofday(&_tv, nullptr);
_thread_name = getThreadName();
}
const string &LogContext::str() {
if (_got_content) {
return _content;
}
_content = ostringstream::str();
_got_content = true;
return _content;
}
///////////////////AsyncLogWriter///////////////////
static string s_module_name = exeName(false);
LogContextCapture::LogContextCapture(Logger &logger, LogLevel level, const char *file, const char *function, int line, const char *flag) :
_ctx(new LogContext(level, file, function, line, s_module_name.c_str() ? s_module_name.c_str() : "", flag)), _logger(logger) {
}
LogContextCapture::LogContextCapture(const LogContextCapture &that) : _ctx(that._ctx), _logger(that._logger) {
const_cast<LogContextPtr &>(that._ctx).reset();
}
LogContextCapture::~LogContextCapture() {
*this << endl;
}
LogContextCapture &LogContextCapture::operator<<(ostream &(*f)(ostream &)) {
if (!_ctx) {
return *this;
}
_logger.write(_ctx);
_ctx.reset();
return *this;
}
void LogContextCapture::clear() {
_ctx.reset();
}
///////////////////AsyncLogWriter///////////////////
AsyncLogWriter::AsyncLogWriter() : _exit_flag(false) {
_thread = std::make_shared<thread>([this]() { this->run(); });
}
AsyncLogWriter::~AsyncLogWriter() {
_exit_flag = true;
_sem.post();
_thread->join();
flushAll();
}
void AsyncLogWriter::write(const LogContextPtr &ctx, Logger &logger) {
{
lock_guard<mutex> lock(_mutex);
_pending.emplace_back(std::make_pair(ctx, &logger));
}
_sem.post();
}
void AsyncLogWriter::run() {
setThreadName("async log");
while (!_exit_flag) {
_sem.wait();
flushAll();
}
}
void AsyncLogWriter::flushAll() {
decltype(_pending) tmp;
{
lock_guard<mutex> lock(_mutex);
tmp.swap(_pending);
}
tmp.for_each([&](std::pair<LogContextPtr, Logger *> &pr) {
pr.second->writeChannels(pr.first);
});
}
///////////////////EventChannel////////////////////
const string EventChannel::kBroadcastLogEvent = "kBroadcastLogEvent";
EventChannel::EventChannel(const string &name, LogLevel level) : LogChannel(name, level) {}
void EventChannel::write(const Logger &logger, const LogContextPtr &ctx) {
if (_level > ctx->_level) {
return;
}
NOTICE_EMIT(BroadcastLogEventArgs, kBroadcastLogEvent, logger, ctx);
}
const std::string &EventChannel::getBroadcastLogEventName() { return kBroadcastLogEvent;}
///////////////////ConsoleChannel///////////////////
ConsoleChannel::ConsoleChannel(const string &name, LogLevel level) : LogChannel(name, level) {}
void ConsoleChannel::write(const Logger &logger, const LogContextPtr &ctx) {
if (_level > ctx->_level) {
return;
}
#if defined(OS_IPHONE)
//ios禁用日志颜色
format(logger, std::cout, ctx, false);
#elif defined(ANDROID)
static android_LogPriority LogPriorityArr[10];
static onceToken s_token([](){
LogPriorityArr[LTrace] = ANDROID_LOG_VERBOSE;
LogPriorityArr[LDebug] = ANDROID_LOG_DEBUG;
LogPriorityArr[LInfo] = ANDROID_LOG_INFO;
LogPriorityArr[LWarn] = ANDROID_LOG_WARN;
LogPriorityArr[LError] = ANDROID_LOG_ERROR;
});
__android_log_print(LogPriorityArr[ctx->_level],"JNI","%s %s",ctx->_function.data(),ctx->str().data());
#else
//linux/windows日志启用颜色并显示日志详情
format(logger, std::cout, ctx);
#endif
}
///////////////////SysLogChannel///////////////////
#if defined(__MACH__) || ((defined(__linux) || defined(__linux__)) && !defined(ANDROID))
SysLogChannel::SysLogChannel(const string &name, LogLevel level) : LogChannel(name, level) {}
void SysLogChannel::write(const Logger &logger, const LogContextPtr &ctx) {
if (_level > ctx->_level) {
return;
}
static int s_syslog_lev[10];
static onceToken s_token([]() {
s_syslog_lev[LTrace] = LOG_DEBUG;
s_syslog_lev[LDebug] = LOG_INFO;
s_syslog_lev[LInfo] = LOG_NOTICE;
s_syslog_lev[LWarn] = LOG_WARNING;
s_syslog_lev[LError] = LOG_ERR;
}, nullptr);
syslog(s_syslog_lev[ctx->_level], "-> %s %d\r\n", ctx->_file.data(), ctx->_line);
syslog(s_syslog_lev[ctx->_level], "## %s %s | %s %s\r\n", printTime(ctx->_tv).data(),
LOG_CONST_TABLE[ctx->_level][2], ctx->_function.data(), ctx->str().data());
}
#endif//#if defined(__MACH__) || ((defined(__linux) || defined(__linux__)) && !defined(ANDROID))
///////////////////LogChannel///////////////////
LogChannel::LogChannel(const string &name, LogLevel level) : _name(name), _level(level) {}
LogChannel::~LogChannel() {}
const string &LogChannel::name() const { return _name; }
void LogChannel::setLevel(LogLevel level) { _level = level; }
std::string LogChannel::printTime(const timeval &tv) {
auto tm = getLocalTime(tv.tv_sec);
char buf[128];
snprintf(buf, sizeof(buf), "%d-%02d-%02d %02d:%02d:%02d.%03d",
1900 + tm.tm_year,
1 + tm.tm_mon,
tm.tm_mday,
tm.tm_hour,
tm.tm_min,
tm.tm_sec,
(int) (tv.tv_usec / 1000));
return buf;
}
#ifdef _WIN32
#define printf_pid() GetCurrentProcessId()
#else
#define printf_pid() getpid()
#endif
void LogChannel::format(const Logger &logger, ostream &ost, const LogContextPtr &ctx, bool enable_color, bool enable_detail) {
if (!enable_detail && ctx->str().empty()) {
// 没有任何信息打印
return;
}
if (enable_color) {
// color console start
#ifdef _WIN32
SetConsoleColor(LOG_CONST_TABLE[ctx->_level][1]);
#else
ost << LOG_CONST_TABLE[ctx->_level][1];
#endif
}
// print log time and level
#ifdef _WIN32
ost << printTime(ctx->_tv) << " " << (char)LOG_CONST_TABLE[ctx->_level][2] << " ";
#else
ost << printTime(ctx->_tv) << " " << LOG_CONST_TABLE[ctx->_level][2] << " ";
#endif
if (enable_detail) {
// tag or process name
ost << "[" << (!ctx->_flag.empty() ? ctx->_flag : logger.getName()) << "] ";
// pid and thread_name
ost << "[" << printf_pid() << "-" << ctx->_thread_name << "] ";
// source file location
ost << ctx->_file << ":" << ctx->_line << " " << ctx->_function << " | ";
}
// log content
ost << ctx->str();
if (enable_color) {
// color console end
#ifdef _WIN32
SetConsoleColor(CLEAR_COLOR);
#else
ost << CLEAR_COLOR;
#endif
}
if (ctx->_repeat > 1) {
// log repeated
ost << "\r\n Last message repeated " << ctx->_repeat << " times";
}
// flush log and new line
ost << endl;
}
///////////////////FileChannelBase///////////////////
FileChannelBase::FileChannelBase(const string &name, const string &path, LogLevel level) : LogChannel(name, level), _path(path) {}
FileChannelBase::~FileChannelBase() {
close();
}
void FileChannelBase::write(const Logger &logger, const std::shared_ptr<LogContext> &ctx) {
if (_level > ctx->_level) {
return;
}
if (!_fstream.is_open()) {
open();
}
//打印至文件,不启用颜色
format(logger, _fstream, ctx, false);
}
bool FileChannelBase::setPath(const string &path) {
_path = path;
return open();
}
const string &FileChannelBase::path() const {
return _path;
}
bool FileChannelBase::open() {
// Ensure a path was set
if (_path.empty()) {
throw runtime_error("Log file path must be set");
}
// Open the file stream
_fstream.close();
#if !defined(_WIN32)
//创建文件夹
File::create_path(_path, S_IRWXO | S_IRWXG | S_IRWXU);
#else
File::create_path(_path,0);
#endif
_fstream.open(_path.data(), ios::out | ios::app);
if (!_fstream.is_open()) {
return false;
}
//打开文件成功
return true;
}
void FileChannelBase::close() {
_fstream.close();
}
size_t FileChannelBase::size() {
return (_fstream << std::flush).tellp();
}
///////////////////FileChannel///////////////////
static const auto s_second_per_day = 24 * 60 * 60;
//根据GMT UNIX时间戳生产日志文件名
static string getLogFilePath(const string &dir, time_t second, int32_t index) {
auto tm = getLocalTime(second);
char buf[64];
snprintf(buf, sizeof(buf), "%d-%02d-%02d_%02d.log", 1900 + tm.tm_year, 1 + tm.tm_mon, tm.tm_mday, index);
return dir + buf;
}
//根据日志文件名返回GMT UNIX时间戳
static time_t getLogFileTime(const string &full_path) {
auto name = getFileName(full_path.data());
struct tm tm{0};
if (!strptime(name, "%Y-%m-%d", &tm)) {
return 0;
}
//此函数会把本地时间转换成GMT时间戳
return mktime(&tm);
}
//获取1970年以来的第几天
static uint64_t getDay(time_t second) {
return (second + getGMTOff()) / s_second_per_day;
}
FileChannel::FileChannel(const string &name, const string &dir, LogLevel level) : FileChannelBase(name, "", level) {
_dir = dir;
if (_dir.back() != '/') {
_dir.append("/");
}
//收集所有日志文件
File::scanDir(_dir, [this](const string &path, bool isDir) -> bool {
if (!isDir && end_with(path, ".log")) {
_log_file_map.emplace(path);
}
return true;
}, false);
//获取今天日志文件的最大index号
auto log_name_prefix = getTimeStr("%Y-%m-%d_");
for (auto it = _log_file_map.begin(); it != _log_file_map.end(); ++it) {
auto name = getFileName(it->data());
//筛选出今天所有的日志文件
if (start_with(name, log_name_prefix)) {
int tm_mday; // day of the month - [1, 31]
int tm_mon; // months since January - [0, 11]
int tm_year; // years since 1900
uint32_t index;
//今天第几个文件
int count = sscanf(name, "%d-%02d-%02d_%d.log", &tm_year, &tm_mon, &tm_mday, &index);
if (count == 4) {
_index = index >= _index ? index : _index;
}
}
}
}
void FileChannel::write(const Logger &logger, const LogContextPtr &ctx) {
//GMT UNIX时间戳
time_t second = ctx->_tv.tv_sec;
//这条日志所在第几天
auto day = getDay(second);
if ((int64_t) day != _last_day) {
if (_last_day != -1) {
//重置日志index
_index = 0;
}
//这条日志是新的一天,记录这一天
_last_day = day;
//获取日志当天对应的文件,每天可能有多个日志切片文件
changeFile(second);
} else {
//检查该天日志是否需要重新分片
checkSize(second);
}
//写日志
if (_can_write) {
FileChannelBase::write(logger, ctx);
}
}
void FileChannel::clean() {
//获取今天是第几天
auto today = getDay(time(nullptr));
//遍历所有日志文件,删除超过若干天前的过期日志文件
for (auto it = _log_file_map.begin(); it != _log_file_map.end();) {
auto day = getDay(getLogFileTime(it->data()));
if (today < day + _log_max_day) {
//这个日志文件距今尚未超过一定天数,后面的文件更新,所以停止遍历
break;
}
//这个文件距今超过了一定天数,则删除文件
File::delete_file(*it);
//删除这条记录
it = _log_file_map.erase(it);
}
//按文件个数清理,限制最大文件切片个数
while (_log_file_map.size() > _log_max_count) {
auto it = _log_file_map.begin();
if (*it == path()) {
//当前文件,停止删除
break;
}
//删除文件
File::delete_file(*it);
//删除这条记录
_log_file_map.erase(it);
}
}
void FileChannel::checkSize(time_t second) {
//每60秒检查一下文件大小防止频繁flush日志文件
if (second - _last_check_time > 60) {
if (FileChannelBase::size() > _log_max_size * 1024 * 1024) {
changeFile(second);
}
_last_check_time = second;
}
}
void FileChannel::changeFile(time_t second) {
auto log_file = getLogFilePath(_dir, second, _index++);
//记录所有的日志文件,以便后续删除老的日志
_log_file_map.emplace(log_file);
//打开新的日志文件
_can_write = setPath(log_file);
if (!_can_write) {
ErrorL << "Failed to open log file: " << _path;
}
//尝试删除过期的日志文件
clean();
}
void FileChannel::setMaxDay(size_t max_day) {
_log_max_day = max_day > 1 ? max_day : 1;
}
void FileChannel::setFileMaxSize(size_t max_size) {
_log_max_size = max_size > 1 ? max_size : 1;
}
void FileChannel::setFileMaxCount(size_t max_count) {
_log_max_count = max_count > 1 ? max_count : 1;
}
//////////////////////////////////////////////////////////////////////////////////////////////////
void LoggerWrapper::printLogV(Logger &logger, int level, const char *file, const char *function, int line, const char *fmt, va_list ap) {
LogContextCapture info(logger, (LogLevel) level, file, function, line);
char *str = nullptr;
if (vasprintf(&str, fmt, ap) >= 0 && str) {
info << str;
#ifdef ASAN_USE_DELETE
delete [] str; // 开启asan后用free会卡死
#else
free(str);
#endif
}
}
void LoggerWrapper::printLog(Logger &logger, int level, const char *file, const char *function, int line, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
printLogV(logger, level, file, function, line, fmt, ap);
va_end(ap);
}
} /* namespace toolkit */