| /* |
| * 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 "util/Log.hpp" |
| |
| #include <algorithm> |
| #include <cctype> |
| #include <chrono> |
| #include <cinttypes> |
| #include <ctime> |
| #include <map> |
| #include <mutex> |
| #include <regex> |
| #include <sstream> |
| #include <string> |
| #include <thread> |
| #include <utility> |
| #include <vector> |
| |
| #include <boost/asio/ip/host_name.hpp> |
| #include <boost/filesystem.hpp> |
| #include <boost/process/environment.hpp> |
| |
| #include <geode/ExceptionTypes.hpp> |
| #include <geode/internal/geode_globals.hpp> |
| #include <geode/util/LogLevel.hpp> |
| |
| #include "geodeBanner.hpp" |
| #include "util/chrono/time_point.hpp" |
| |
| namespace { |
| |
| static size_t g_bytesWritten = 0; |
| |
| static size_t g_fileSizeLimit = GEODE_MAX_LOG_FILE_LIMIT; |
| static size_t g_diskSpaceLimit = GEODE_MAX_LOG_DISK_LIMIT; |
| |
| static std::mutex g_logMutex; |
| |
| static int g_rollIndex = 0; |
| static size_t g_spaceUsed = 0; |
| |
| static boost::filesystem::path g_fullpath; |
| static std::map<int32_t, boost::filesystem::path> g_rollFiles; |
| |
| static FILE* g_log = nullptr; |
| |
| static std::string g_hostName; |
| |
| const int __1K__ = 1024; |
| const int __1M__ = (__1K__ * __1K__); |
| |
| } // namespace |
| |
| namespace apache { |
| namespace geode { |
| namespace client { |
| |
| LogLevel Log::s_logLevel = LogLevel::Default; |
| |
| /*****************************************************************************/ |
| |
| LogLevel Log::logLevel() { return s_logLevel; } |
| |
| /** |
| * Set the current log level. |
| */ |
| void Log::setLogLevel(LogLevel level) { s_logLevel = level; } |
| |
| void Log::validateSizeLimits(int64_t fileSizeLimit, int64_t diskSpaceLimit) { |
| if (fileSizeLimit * __1M__ > GEODE_MAX_LOG_FILE_LIMIT) { |
| throw IllegalArgumentException( |
| "Specified file size limit larger than max allowed (1GB)"); |
| } else if (fileSizeLimit < 0) { |
| throw IllegalArgumentException("Specified file size limit must be >= 0"); |
| } |
| |
| if (diskSpaceLimit * __1M__ > GEODE_MAX_LOG_DISK_LIMIT) { |
| throw IllegalArgumentException( |
| "Specified disk space limit larger than max allowed (1TB)"); |
| } else if (diskSpaceLimit < 0) { |
| throw IllegalArgumentException("Specified disk space limit must be >= 0"); |
| } |
| |
| if (fileSizeLimit > diskSpaceLimit && diskSpaceLimit != 0) { |
| throw IllegalArgumentException( |
| "Disk space limit must be larger than file size limit"); |
| } |
| } |
| |
| void Log::init(LogLevel level, const char* logFileName, int32_t logFileLimit, |
| int64_t logDiskSpaceLimit) { |
| auto logFileNameString = |
| logFileName ? std::string(logFileName) : std::string("geode-native.log"); |
| init(level, logFileNameString, logFileLimit, logDiskSpaceLimit); |
| } |
| |
| void Log::rollLogFile() { |
| if (g_log) { |
| fclose(g_log); |
| g_log = nullptr; |
| } |
| |
| auto rollFileName = |
| (g_fullpath.parent_path() / |
| (g_fullpath.stem().string() + "-" + std::to_string(g_rollIndex) + |
| g_fullpath.extension().string())) |
| .string(); |
| try { |
| auto rollFile = boost::filesystem::path(rollFileName); |
| boost::filesystem::rename(g_fullpath, rollFile); |
| g_rollFiles[g_rollIndex] = rollFile; |
| g_rollIndex++; |
| } catch (const boost::filesystem::filesystem_error&) { |
| throw IllegalStateException("Failed to roll log file"); |
| } |
| } |
| |
| void Log::removeOldestRolledLogFile() { |
| if (g_rollFiles.size()) { |
| auto index = g_rollFiles.begin()->first; |
| auto fileToRemove = g_rollFiles.begin()->second; |
| auto fileSize = boost::filesystem::file_size(fileToRemove); |
| boost::filesystem::remove(fileToRemove); |
| g_rollFiles.erase(index); |
| g_spaceUsed -= fileSize; |
| } else { |
| throw IllegalStateException( |
| "Failed to free sufficient disk space for logs"); |
| } |
| } |
| |
| void Log::calculateUsedDiskSpace() { |
| g_spaceUsed = 0; |
| if (boost::filesystem::exists(g_fullpath)) { |
| g_spaceUsed = boost::filesystem::file_size(g_fullpath); |
| for (auto const& item : g_rollFiles) { |
| g_spaceUsed += boost::filesystem::file_size(item.second); |
| } |
| } |
| } |
| |
| void Log::buildRollFileMapping() { |
| const auto filterstring = g_fullpath.stem().string() + "-(\\d+)\\.log$"; |
| const std::regex my_filter(filterstring); |
| |
| g_rollFiles.clear(); |
| |
| boost::filesystem::directory_iterator end_itr; |
| for (boost::filesystem::directory_iterator i( |
| g_fullpath.parent_path().string()); |
| i != end_itr; ++i) { |
| if (boost::filesystem::is_regular_file(i->status())) { |
| std::string filename = i->path().filename().string(); |
| std::regex testPattern(filterstring); |
| std::match_results<std::string::const_iterator> testMatches; |
| if (std::regex_search(std::string::const_iterator(filename.begin()), |
| filename.cend(), testMatches, testPattern)) { |
| auto index = std::atoi( |
| std::string(testMatches[1].first, testMatches[1].second).c_str()); |
| g_rollFiles[index] = i->path(); |
| } |
| } |
| } |
| } |
| |
| void Log::setRollFileIndex() { |
| g_rollIndex = 0; |
| if (g_rollFiles.size()) { |
| g_rollIndex = g_rollFiles.rbegin()->first + 1; |
| } |
| } |
| |
| void Log::setSizeLimits(int32_t logFileLimit, int64_t logDiskSpaceLimit) { |
| validateSizeLimits(logFileLimit, logDiskSpaceLimit); |
| |
| // Default to 10MB file limit and 1GB disk limit |
| if (logFileLimit == 0 && logDiskSpaceLimit == 0) { |
| g_fileSizeLimit = 10 * __1M__; |
| g_diskSpaceLimit = 1000 * __1M__; |
| } |
| // disk space specified but file size is defaulted. Just use a single |
| // log file, i.e. set file limit == disk limit |
| else if (logFileLimit == 0) { |
| g_diskSpaceLimit = logDiskSpaceLimit * __1M__; |
| g_fileSizeLimit = g_diskSpaceLimit; |
| } else if (logDiskSpaceLimit == 0) { |
| g_fileSizeLimit = logFileLimit * __1M__; |
| g_diskSpaceLimit = g_fileSizeLimit; |
| } else { |
| g_fileSizeLimit = logFileLimit * __1M__; |
| g_diskSpaceLimit = logDiskSpaceLimit * __1M__; |
| } |
| } |
| |
| void Log::init(LogLevel level, const std::string& logFileName, |
| int32_t logFileLimit, int64_t logDiskSpaceLimit) { |
| if (g_log != nullptr) { |
| throw IllegalStateException( |
| "The Log has already been initialized. " |
| "Call Log::close() before calling Log::init again."); |
| } |
| s_logLevel = level; |
| |
| try { |
| std::lock_guard<decltype(g_logMutex)> guard(g_logMutex); |
| |
| g_hostName = boost::asio::ip::host_name(); |
| |
| g_fullpath = |
| boost::filesystem::absolute(boost::filesystem::path(logFileName)); |
| |
| // if no extension then add .log extension |
| if (g_fullpath.extension().empty() || (g_fullpath.extension() != ".log")) { |
| g_fullpath = g_fullpath.string() + ".log"; |
| } |
| |
| setSizeLimits(logFileLimit, logDiskSpaceLimit); |
| |
| g_bytesWritten = 0; |
| g_spaceUsed = 0; |
| |
| // Ensure that directory exists for log files. We're going to attempt |
| // to iterate through files in that folder, and if it doesn't exist boost |
| // will throw an exception. |
| const auto target_path = g_fullpath.parent_path().string(); |
| if (!boost::filesystem::exists(target_path)) { |
| boost::filesystem::create_directories(target_path); |
| } |
| |
| buildRollFileMapping(); |
| setRollFileIndex(); |
| calculateUsedDiskSpace(); |
| while (g_spaceUsed > g_diskSpaceLimit) { |
| removeOldestRolledLogFile(); |
| } |
| |
| if (boost::filesystem::exists(g_fullpath) && logFileLimit > 0) { |
| rollLogFile(); |
| } |
| writeBanner(); |
| } catch (const boost::exception&) { |
| auto msg = std::string("Unable to log to file '") + logFileName + "'"; |
| throw IllegalArgumentException(msg.c_str()); |
| } catch (const std::exception& ex) { |
| auto msg = std::string("Unable to log to file '") + logFileName + |
| "': " + ex.what(); |
| throw IllegalArgumentException(msg.c_str()); |
| } |
| } |
| |
| void Log::close() { |
| std::lock_guard<decltype(g_logMutex)> guard(g_logMutex); |
| |
| if (g_log) { |
| fclose(g_log); |
| g_log = nullptr; |
| } |
| g_fullpath = ""; |
| } |
| |
| void Log::writeBanner() { |
| if (s_logLevel != LogLevel::None) { |
| std::string bannertext = geodeBanner::getBanner(); |
| |
| // fullpath empty --> we're logging to stdout |
| if (g_fullpath.string().empty()) { |
| fprintf(stdout, "%s", bannertext.c_str()); |
| fflush(stdout); |
| } else { |
| if (boost::filesystem::exists( |
| g_fullpath.parent_path().string().c_str()) || |
| boost::filesystem::create_directories(g_fullpath.parent_path())) { |
| g_log = fopen(g_fullpath.string().c_str(), "a"); |
| if (g_log) { |
| if (fprintf(g_log, "%s", bannertext.c_str())) { |
| g_bytesWritten += static_cast<int32_t>(bannertext.length()); |
| fflush(g_log); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| const char* Log::levelToChars(LogLevel level) { |
| switch (level) { |
| case LogLevel::None: |
| return "none"; |
| |
| case LogLevel::Error: |
| return "error"; |
| |
| case LogLevel::Warning: |
| return "warning"; |
| |
| case LogLevel::Info: |
| return "info"; |
| |
| case LogLevel::Default: |
| return "default"; |
| |
| case LogLevel::Config: |
| return "config"; |
| |
| case LogLevel::Fine: |
| return "fine"; |
| |
| case LogLevel::Finer: |
| return "finer"; |
| |
| case LogLevel::Finest: |
| return "finest"; |
| |
| case LogLevel::Debug: |
| return "debug"; |
| |
| case LogLevel::All: |
| return "all"; |
| } |
| throw IllegalArgumentException(std::string("Unexpected log level: ") + |
| std::to_string(static_cast<int>(level))); |
| } |
| |
| LogLevel Log::charsToLevel(const std::string& chars) { |
| std::string level = chars; |
| |
| if (level.empty()) return LogLevel::None; |
| |
| std::transform(level.begin(), level.end(), level.begin(), ::tolower); |
| |
| if (level == "none") { |
| return LogLevel::None; |
| } else if (level == "error") { |
| return LogLevel::Error; |
| } else if (level == "warning") { |
| return LogLevel::Warning; |
| } else if (level == "info") { |
| return LogLevel::Info; |
| } else if (level == "default") { |
| return LogLevel::Default; |
| } else if (level == "config") { |
| return LogLevel::Config; |
| } else if (level == "fine") { |
| return LogLevel::Fine; |
| } else if (level == "finer") { |
| return LogLevel::Finer; |
| } else if (level == "finest") { |
| return LogLevel::Finest; |
| } else if (level == "debug") { |
| return LogLevel::Debug; |
| } else if (level == "all") { |
| return LogLevel::All; |
| } else { |
| throw IllegalArgumentException(("Unexpected log level: " + level).c_str()); |
| } |
| } |
| |
| std::string Log::formatLogLine(LogLevel level) { |
| std::stringstream msg; |
| const size_t MINBUFSIZE = 128; |
| auto now = std::chrono::system_clock::now(); |
| auto secs = std::chrono::system_clock::to_time_t(now); |
| auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>( |
| now - std::chrono::system_clock::from_time_t(secs)); |
| auto tm_val = apache::geode::util::chrono::localtime(secs); |
| |
| msg << "[" << Log::levelToChars(level) << " " |
| << std::put_time(&tm_val, "%Y/%m/%d %H:%M:%S") << '.' << std::setfill('0') |
| << std::setw(6) << microseconds.count() << ' ' |
| << std::put_time(&tm_val, "%Z ") << g_hostName << ":" |
| << boost::this_process::get_id() << " " << std::this_thread::get_id() |
| << "] "; |
| |
| return msg.str(); |
| } |
| |
| void Log::log(LogLevel level, const std::string& msg) { |
| Log::logInternal(level, msg); |
| } |
| |
| void Log::logInternal(LogLevel level, const std::string& msg) { |
| std::lock_guard<decltype(g_logMutex)> guard(g_logMutex); |
| |
| std::string buf; |
| char fullpath[512] = {0}; |
| |
| if (g_fullpath.string().empty()) { |
| fprintf(stdout, "%s%s\n", formatLogLine(level).c_str(), msg.c_str()); |
| fflush(stdout); |
| } else { |
| if (!g_log) { |
| g_log = fopen(g_fullpath.string().c_str(), "a"); |
| } |
| |
| if (g_log) { |
| buf = formatLogLine(level); |
| auto numChars = static_cast<int>(buf.length() + msg.length()); |
| g_bytesWritten += |
| numChars + 2; // bcoz we have to count trailing new line (\n) |
| |
| if ((g_fileSizeLimit != 0) && (g_bytesWritten >= g_fileSizeLimit)) { |
| rollLogFile(); |
| g_bytesWritten = numChars + 2; // Account for trailing newline |
| writeBanner(); |
| } |
| |
| g_spaceUsed += numChars + 2; |
| |
| // Remove existing rolled log files until we're below the limit |
| while (g_spaceUsed >= g_diskSpaceLimit) { |
| removeOldestRolledLogFile(); |
| } |
| |
| if ((numChars = fprintf(g_log, "%s%s\n", buf.c_str(), msg.c_str())) == |
| 0 || |
| ferror(g_log)) { |
| // Let's continue without throwing the exception. It should not cause |
| // process to terminate |
| fclose(g_log); |
| g_log = nullptr; |
| } else { |
| fflush(g_log); |
| } |
| } |
| } |
| } |
| |
| #ifdef _WIN32 |
| #define vsnprintf _vsnprintf |
| #endif |
| |
| void Log::log(LogLevel level, const char* fmt, ...) { |
| char msg[_GF_MSG_LIMIT] = {0}; |
| va_list argp; |
| va_start(argp, fmt); |
| vsnprintf(msg, _GF_MSG_LIMIT, fmt, argp); |
| /* win doesn't guarantee termination */ msg[_GF_MSG_LIMIT - 1] = '\0'; |
| Log::logInternal(level, std::string(msg)); |
| va_end(argp); |
| } |
| |
| void Log::logCatch(LogLevel level, const char* msg, const Exception& ex) { |
| if (enabled(level)) { |
| std::string message = "Geode exception " + ex.getName() + |
| " caught: " + ex.getMessage() + "\n" + msg; |
| log(level, message); |
| } |
| } |
| |
| bool Log::enabled(LogLevel level) { |
| return (level != LogLevel::None && level <= logLevel()); |
| } |
| |
| } // namespace client |
| } // namespace geode |
| } // namespace apache |