/* * 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 #include #include #include "logger.h" #include "onceToken.h" #include "File.h" #include "NoticeCenter.h" #if defined(_WIN32) #include "strptime_win.h" #endif #ifdef ANDROID #include #endif //ANDROID #if defined(__MACH__) || ((defined(__linux) || defined(__linux__)) && !defined(ANDROID)) #include #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(); _default_channel = std::make_shared("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 &channel) { _channels[channel->name()] = channel; } void Logger::del(const string &name) { _channels.erase(name); } std::shared_ptr 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 &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(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([this]() { this->run(); }); } AsyncLogWriter::~AsyncLogWriter() { _exit_flag = true; _sem.post(); _thread->join(); flushAll(); } void AsyncLogWriter::write(const LogContextPtr &ctx, Logger &logger) { { lock_guard 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 lock(_mutex); tmp.swap(_pending); } tmp.for_each([&](std::pair &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 &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 */