/* * Copyright (c) 2013-2015, dennis wang * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #if defined(_WIN32) || defined(WIN32) || defined(_WIN64) #include #else #include #include #endif // _WIN32 #include "file_appender.h" #include "logger_impl.h" #include "util.h" // Temp buffer thread_local static std::unique_ptr buffer_ptr_; FileAppender::FileAppender(LoggerImpl *impl, const std::string &name, const std::string &attribute) { if (!name.empty()) { _name = "[" + name + "]"; _realName = name; } _impl = impl; // 获取配置 parseAttribute(attribute); if (_async) { // 异步模式 // 启动异步日志线程 _worker = std::thread([&]() { std::string suffix; _running = true; while (_running) { // 是否需要睡眠一个短暂时间 bool need_pause = false; // 当前日志行 LogLine line; { std::lock_guard guard(_fpLock); if (!_logList.empty()) { line = std::move(_logList.back()); _logList.pop_back(); } else { need_pause = true; } } if (line.log) { // 有日志行需要写入 suffix.clear(); if (_same_log_count) { auto it = log_map_.find(line.log.get() + line.prefix_size); if (it != log_map_.end()) { it->second += 1; suffix.append("{").append(std::to_string(it->second)).append("}"); } else { log_map_.emplace(line.log.get() + line.prefix_size, 0); } } std::fprintf(_fp, "%s%s%s", line.log.get(), suffix.c_str(), LINE_END); if (_flush) { std::fflush(_fp); } } if (need_pause) { sleep(1); } } }); } } FileAppender::~FileAppender() {} void FileAppender::write(int level, const char *format, ...) { if (level < _level) { return; } open(); // 切换或第一次建立文件 if (!_fp) { return; } va_list va_ptr; va_start(va_ptr, format); write(getLevelString(level), format, va_ptr); va_end(va_ptr); } void FileAppender::setLevel(int level) { _level = level; } int FileAppender::getLevel() { return _level; } void FileAppender::destroy() { if (_async) { _running = false; // 停止 if (_worker.joinable()) { _worker.join(); } } // 输出所有未被写入的log LogList::iterator guard = _logList.begin(); for (; guard != _logList.end(); guard++) { if (_fp) { std::fprintf(_fp, "%s%s", guard->log.get(), LINE_END); } } if (_fp) { std::fflush(_fp); std::fclose(_fp); } _fp = nullptr; // 从管理器内删除 if (_impl) { _impl->remove(_realName); } // 销毁自己 delete this; } static bool pathExists(const std::string &path) { #if defined(_WIN32) || defined(WIN32) || defined(_WIN64) return !_access(path.c_str(), 0); #else return (access(path.c_str(), F_OK) != -1); #endif // _WIN32 } static size_t fileSize(const std::string &path) { size_t size = (size_t)-1; FILE *fp{nullptr}; fp = std::fopen(path.c_str(), "r"); if (!fp) { return size; } std::fseek(fp, 0L, SEEK_END); size = std::ftell(fp); std::fclose(fp); return size; } void FileAppender::open() { std::lock_guard guard(_fpLock); if (_logMode.reach() || (_size > _maxSize)) { if (_size <= _maxSize) { _index = 0; } // 需要切换文件 if (_fp) { std::fclose(_fp); } _fp = nullptr; _size = 0; } if (!_fp) { // 建立日志文件 std::string fileName; std::string rootPath = _path + _logMode.getFileName(); for (;;) { fileName = rootPath; std::stringstream ss; ss << "." << _index++; fileName += ss.str(); if (!pathExists(fileName)) { break; } if (_useLastFile && (fileSize(fileName) < (size_t)_maxSize)) { break; } } _fp = std::fopen(fileName.c_str(), "a+"); if (!_fp) { return; } } } void FileAppender::write(const std::string &level, const char *format, va_list va_ptr) { if (_format == Format::JSON) { // 写入JSON格式 write_json(level, format, va_ptr); return; } std::string pattern = _pattern.getPrefix(); int len = 0; if (!buffer_ptr_) { buffer_ptr_ = std::make_unique(BUF_LEN); } char *buffer = buffer_ptr_.get(); if (!pattern.empty()) { // 日志行前缀 std::memcpy(buffer + len, pattern.c_str(), (int)pattern.size()); len += (int)pattern.size(); } if (show_log_level_ && !level.empty()) { // 日志等级 std::memcpy(buffer + len, level.c_str(), (int)level.size()); len += (int)level.size(); } if (show_log_name_ && !_name.empty()) { // 日志名称 std::memcpy(buffer + len, _name.c_str(), (int)_name.size()); len += (int)_name.size(); } auto prefix_size = len; int bytes = std::vsnprintf(buffer + len, BUF_LEN - len - 1, format, va_ptr); if (0 >= bytes) { return; } len += bytes; _size += bytes; if (_async) { // 写入到异步队列 auto log = std::make_unique(std::size_t(len) + 1); std::memcpy(log.get(), buffer, len); log[len] = 0; std::lock_guard guard(_fpLock); _logList.push_front({std::move(log), prefix_size}); } else { // 同步写入 std::string suffix; if (_same_log_count) { auto it = log_map_.find(buffer + prefix_size); if (it != log_map_.end()) { it->second += 1; suffix.append("{").append(std::to_string(it->second)).append("}"); } else { log_map_.emplace(buffer, prefix_size); } } std::fprintf(_fp, "%s%s%s", buffer, suffix.c_str(), LINE_END); if (_flush) { std::fflush(_fp); } } } void FileAppender::write_json(const std::string &level, const char *format, va_list va_ptr) { if (!buffer_ptr_) { buffer_ptr_.reset(new char[BUF_LEN]); } char *buffer = buffer_ptr_.get(); int bytes = std::vsnprintf(buffer, BUF_LEN - 1, format, va_ptr); if (0 >= bytes) { return; } std::string json = "{"; if (show_log_level_) { json.append("\"level\":\"").append(level).append("\","); } json.append("\"time\":\"").append(_pattern.getTime()).append("\","); if (show_log_name_) { json.append("\"logger\":\"").append(_name).append("\","); } json.append("\"message\":").append("\"").append(buffer).append("\""); json += "}"; if (_async) { // 写入到异步队列 auto log = std::make_unique(json.size() + 1); std::memcpy(log.get(), json.c_str(), json.size()); log[json.size()] = 0; std::lock_guard guard(_fpLock); _logList.push_front({std::move(log), 0}); } else { // 同步写入 std::string suffix; if (_same_log_count) { auto it = log_map_.find(json); if (it != log_map_.end()) { it->second += 1; suffix.append("{").append(std::to_string(it->second)).append("}"); } else { log_map_.emplace(json, 0); } } std::fprintf(_fp, "%s%s%s", json.c_str(), suffix.c_str(), LINE_END); if (_flush) { std::fflush(_fp); } } } void FileAppender::reload(const std::string &attribute) { parseAttribute(attribute); } void FileAppender::show_level(bool flag) { show_log_level_ = flag; } void FileAppender::show_name(bool flag) { show_log_name_ = flag; } void FileAppender::parseAttribute(const std::string &attribute) { std::vector tokens; klogger::split(attribute, ";", tokens); std::vector::iterator guard = tokens.begin(); // 解析日志属性配置 for (; guard != tokens.end(); guard++) { if (std::string::npos != guard->find("file://")) { _path = getAttribute("file://", *guard); if (_path.at(_path.size() - 1) != SEPERATOR) { _path += SEPERATOR; } } else if (std::string::npos != guard->find("mode=")) { _mode = getAttribute("mode=", *guard); } else if (std::string::npos != guard->find("file=")) { _file = getAttribute("file=", *guard); } else if (std::string::npos != guard->find("async=")) { std::string async = getAttribute("async=", *guard); _async = (async == "true"); } else if (std::string::npos != guard->find("line=")) { _line = getAttribute("line=", *guard); _pattern.parse(_line); } else if (std::string::npos != guard->find("flush=")) { std::string flush = getAttribute("flush=", *guard); _flush = (flush == "true"); } else if (std::string::npos != guard->find("level=")) { std::string level = getAttribute("level=", *guard); _level = atoi(level.c_str()); } else if (std::string::npos != guard->find("maxSize=")) { std::string maxSize = getAttribute("maxSize=", *guard); _maxSize = getMaxSize(maxSize); } else if (std::string::npos != guard->find("useLastFile=")) { std::string maxSize = getAttribute("useLastFile=", *guard); _useLastFile = (maxSize == "true"); } else if (std::string::npos != guard->find("logCount=")) { auto log_count = getAttribute("logCount=", *guard); _same_log_count = (log_count == "true"); } else if (std::string::npos != guard->find("showLevel=")) { std::string attr = getAttribute("showLevel=", *guard); show_log_level_ = (attr == "true"); } else if (std::string::npos != guard->find("showName=")) { std::string attr = getAttribute("showName=", *guard); show_log_name_ = (attr == "true"); } else if (std::string::npos != guard->find("format=")) { auto format = getAttribute("format=", *guard); if (format == "json") { _format = Format::JSON; } else { _format = Format::TXT; } } } if (!_mode.empty()) { _logMode.parse(_mode, _file); } } int FileAppender::getMaxSize(const std::string &str) { if (str.empty()) { return _maxSize; } int dem = 1; char last = str[str.length() - 1]; if (last == 'K' || last == 'k') { dem = 1024; } else if (last == 'M' || last == 'm') { dem = 1024 * 1024; } else if (last == 'G' || last == 'g') { dem = 1024 * 1024 * 1024; } if (dem > 1) { if (str.size() < 2) { return _maxSize; } std::string value; value.assign(str, 0, str.size() - 1); return (dem * std::stoi(value)); } return std::stoi(str); } std::string FileAppender::getAttribute(const std::string &name, const std::string &attribute) { size_t pos = attribute.find(name); if (pos == std::string::npos) { throw LoggerException("invalid attribute"); } return attribute.substr(pos + name.size()); }