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 | |
22 | namespace leveldb { |
23 | |
24 | class 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 | |