- // 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 <sys/time.h>
-
- #include <cassert>
- #include <cinttypes>
- #include <cstdarg>
- #include <cstdio>
- #include <ctime>
-
- #include "leveldb/env.h"
-
- namespace leveldb {
-
- class PosixLogger final : public Logger {
- public:
- PosixLogger(FILE* fp, uint64_t (*gettid)()) : fp_(fp), gettid_(gettid) {
- assert(fp != nullptr);
- }
-
- ~PosixLogger() override {
- std::fclose(fp_);
- }
-
- void Logv(const char* format, 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);
-
- const uint64_t thread_id = (*gettid_)();
-
- // 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<size_t>(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 = snprintf(
- buffer, buffer_size,
- "%04d/%02d/%02d-%02d:%02d:%02d.%06d %" PRIx64 " ",
- 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<int>(now_timeval.tv_usec),
- thread_id);
-
- // The header can be at most 48 characters (10 date + 15 time + 3 spacing
- // + 20 thread ID), which should fit comfortably into the static buffer.
- assert(buffer_offset <= 48);
- static_assert(48 < 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 (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_;
- uint64_t (* const gettid_)(); // Return the thread id for the current thread.
- };
-
- } // namespace leveldb
-
- #endif // STORAGE_LEVELDB_UTIL_POSIX_LOGGER_H_
|