// Copyright (c) 2011 The LevelDB Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. See the AUTHORS file for names of contributors. // // Logger implementation that can be shared by all environments // where enough posix functionality is available. #ifndef STORAGE_LEVELDB_UTIL_POSIX_LOGGER_H_ #define STORAGE_LEVELDB_UTIL_POSIX_LOGGER_H_ #include #include #include #include #include #include #include #include "leveldb/env.h" namespace leveldb { class PosixLogger final : public Logger { public: // Creates a logger that writes to the given file. // // The PosixLogger instance takes ownership of the file handle. explicit PosixLogger(std::FILE* fp) : fp_(fp) { assert(fp != nullptr); } ~PosixLogger() override { std::fclose(fp_); } void Logv(const char* format, std::va_list arguments) override { // Record the time as close to the Logv() call as possible. struct ::timeval now_timeval; ::gettimeofday(&now_timeval, nullptr); const std::time_t now_seconds = now_timeval.tv_sec; struct std::tm now_components; ::localtime_r(&now_seconds, &now_components); // Record the thread ID. constexpr const int kMaxThreadIdSize = 32; std::ostringstream thread_stream; thread_stream << std::this_thread::get_id(); std::string thread_id = thread_stream.str(); if (thread_id.size() > kMaxThreadIdSize) { thread_id.resize(kMaxThreadIdSize); } // We first attempt to print into a stack-allocated buffer. If this attempt // fails, we make a second attempt with a dynamically allocated buffer. constexpr const int kStackBufferSize = 512; char stack_buffer[kStackBufferSize]; static_assert(sizeof(stack_buffer) == static_cast(kStackBufferSize), "sizeof(char) is expected to be 1 in C++"); int dynamic_buffer_size = 0; // Computed in the first iteration. for (int iteration = 0; iteration < 2; ++iteration) { const int buffer_size = (iteration == 0) ? kStackBufferSize : dynamic_buffer_size; char* const buffer = (iteration == 0) ? stack_buffer : new char[dynamic_buffer_size]; // Print the header into the buffer. int buffer_offset = std::snprintf( buffer, buffer_size, "%04d/%02d/%02d-%02d:%02d:%02d.%06d %s ", now_components.tm_year + 1900, now_components.tm_mon + 1, now_components.tm_mday, now_components.tm_hour, now_components.tm_min, now_components.tm_sec, static_cast(now_timeval.tv_usec), thread_id.c_str()); // The header can be at most 28 characters (10 date + 15 time + // 3 delimiters) plus the thread ID, which should fit comfortably into the // static buffer. assert(buffer_offset <= 28 + kMaxThreadIdSize); static_assert(28 + kMaxThreadIdSize < kStackBufferSize, "stack-allocated buffer may not fit the message header"); assert(buffer_offset < buffer_size); // Print the message into the buffer. std::va_list arguments_copy; va_copy(arguments_copy, arguments); buffer_offset += std::vsnprintf(buffer + buffer_offset, buffer_size - buffer_offset, format, arguments_copy); va_end(arguments_copy); // The code below may append a newline at the end of the buffer, which // requires an extra character. if (buffer_offset >= buffer_size - 1) { // The message did not fit into the buffer. if (iteration == 0) { // Re-run the loop and use a dynamically-allocated buffer. The buffer // will be large enough for the log message, an extra newline and a // null terminator. dynamic_buffer_size = buffer_offset + 2; continue; } // The dynamically-allocated buffer was incorrectly sized. This should // not happen, assuming a correct implementation of std::(v)snprintf. // Fail in tests, recover by truncating the log message in production. assert(false); buffer_offset = buffer_size - 1; } // Add a newline if necessary. if (buffer[buffer_offset - 1] != '\n') { buffer[buffer_offset] = '\n'; ++buffer_offset; } assert(buffer_offset <= buffer_size); std::fwrite(buffer, 1, buffer_offset, fp_); std::fflush(fp_); if (iteration != 0) { delete[] buffer; } break; } } private: std::FILE* const fp_; }; } // namespace leveldb #endif // STORAGE_LEVELDB_UTIL_POSIX_LOGGER_H_