1// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file. See the AUTHORS file for names of contributors.
4//
5// Logger implementation that can be shared by all environments
6// where enough posix functionality is available.
7
8#ifndef STORAGE_LEVELDB_UTIL_POSIX_LOGGER_H_
9#define STORAGE_LEVELDB_UTIL_POSIX_LOGGER_H_
10
11#include <sys/time.h>
12
13#include <cassert>
14#include <cstdarg>
15#include <cstdio>
16#include <ctime>
17#include <sstream>
18#include <thread>
19
20#include "leveldb/env.h"
21
22namespace leveldb {
23
24class PosixLogger final : public Logger {
25 public:
26 // Creates a logger that writes to the given file.
27 //
28 // The PosixLogger instance takes ownership of the file handle.
29 explicit PosixLogger(std::FILE* fp) : fp_(fp) { assert(fp != nullptr); }
30
31 ~PosixLogger() override { std::fclose(fp_); }
32
33 void Logv(const char* format, std::va_list arguments) override {
34 // Record the time as close to the Logv() call as possible.
35 struct ::timeval now_timeval;
36 ::gettimeofday(&now_timeval, nullptr);
37 const std::time_t now_seconds = now_timeval.tv_sec;
38 struct std::tm now_components;
39 ::localtime_r(&now_seconds, &now_components);
40
41 // Record the thread ID.
42 constexpr const int kMaxThreadIdSize = 32;
43 std::ostringstream thread_stream;
44 thread_stream << std::this_thread::get_id();
45 std::string thread_id = thread_stream.str();
46 if (thread_id.size() > kMaxThreadIdSize) {
47 thread_id.resize(kMaxThreadIdSize);
48 }
49
50 // We first attempt to print into a stack-allocated buffer. If this attempt
51 // fails, we make a second attempt with a dynamically allocated buffer.
52 constexpr const int kStackBufferSize = 512;
53 char stack_buffer[kStackBufferSize];
54 static_assert(sizeof(stack_buffer) == static_cast<size_t>(kStackBufferSize),
55 "sizeof(char) is expected to be 1 in C++");
56
57 int dynamic_buffer_size = 0; // Computed in the first iteration.
58 for (int iteration = 0; iteration < 2; ++iteration) {
59 const int buffer_size =
60 (iteration == 0) ? kStackBufferSize : dynamic_buffer_size;
61 char* const buffer =
62 (iteration == 0) ? stack_buffer : new char[dynamic_buffer_size];
63
64 // Print the header into the buffer.
65 int buffer_offset = std::snprintf(
66 buffer, buffer_size, "%04d/%02d/%02d-%02d:%02d:%02d.%06d %s ",
67 now_components.tm_year + 1900, now_components.tm_mon + 1,
68 now_components.tm_mday, now_components.tm_hour, now_components.tm_min,
69 now_components.tm_sec, static_cast<int>(now_timeval.tv_usec),
70 thread_id.c_str());
71
72 // The header can be at most 28 characters (10 date + 15 time +
73 // 3 delimiters) plus the thread ID, which should fit comfortably into the
74 // static buffer.
75 assert(buffer_offset <= 28 + kMaxThreadIdSize);
76 static_assert(28 + kMaxThreadIdSize < kStackBufferSize,
77 "stack-allocated buffer may not fit the message header");
78 assert(buffer_offset < buffer_size);
79
80 // Print the message into the buffer.
81 std::va_list arguments_copy;
82 va_copy(arguments_copy, arguments);
83 buffer_offset +=
84 std::vsnprintf(buffer + buffer_offset, buffer_size - buffer_offset,
85 format, arguments_copy);
86 va_end(arguments_copy);
87
88 // The code below may append a newline at the end of the buffer, which
89 // requires an extra character.
90 if (buffer_offset >= buffer_size - 1) {
91 // The message did not fit into the buffer.
92 if (iteration == 0) {
93 // Re-run the loop and use a dynamically-allocated buffer. The buffer
94 // will be large enough for the log message, an extra newline and a
95 // null terminator.
96 dynamic_buffer_size = buffer_offset + 2;
97 continue;
98 }
99
100 // The dynamically-allocated buffer was incorrectly sized. This should
101 // not happen, assuming a correct implementation of std::(v)snprintf.
102 // Fail in tests, recover by truncating the log message in production.
103 assert(false);
104 buffer_offset = buffer_size - 1;
105 }
106
107 // Add a newline if necessary.
108 if (buffer[buffer_offset - 1] != '\n') {
109 buffer[buffer_offset] = '\n';
110 ++buffer_offset;
111 }
112
113 assert(buffer_offset <= buffer_size);
114 std::fwrite(buffer, 1, buffer_offset, fp_);
115 std::fflush(fp_);
116
117 if (iteration != 0) {
118 delete[] buffer;
119 }
120 break;
121 }
122 }
123
124 private:
125 std::FILE* const fp_;
126};
127
128} // namespace leveldb
129
130#endif // STORAGE_LEVELDB_UTIL_POSIX_LOGGER_H_
131