summaryrefslogtreecommitdiff
path: root/chromium/third_party/leveldatabase/chromium_logger.h
blob: 08317fef9c42a2d72cc64cc03ee7181f2bc52002 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef THIRD_PARTY_LEVELDATABASE_CHROMIUM_LOGGER_H_
#define THIRD_PARTY_LEVELDATABASE_CHROMIUM_LOGGER_H_

#include <cstdarg>
#include <utility>

#include "base/files/file.h"
#include "base/format_macros.h"
#include "base/strings/string_util.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"

namespace leveldb {

class ChromiumLogger : public Logger {
 public:
  explicit ChromiumLogger(base::File file) : file_(std::move(file)) {}

  ~ChromiumLogger() override = default;

  void Logv(const char* format, va_list arguments) override {
    // Record the time as close to the Logv() call as possible.
    base::Time::Exploded now_exploded;
    base::Time::Now().LocalExplode(&now_exploded);

    const base::PlatformThreadId thread_id = base::PlatformThread::CurrentId();

    // 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 = base::snprintf(
          buffer, buffer_size,
          "%04d/%02d/%02d-%02d:%02d:%02d.%03d %" PRIx64 " ",
          now_exploded.year,
          now_exploded.month,
          now_exploded.day_of_month,
          now_exploded.hour,
          now_exploded.minute,
          now_exploded.second,
          now_exploded.millisecond,
          static_cast<uint64_t>(thread_id));

      // The header can be at most 45 characters (10 date + 12 time + 3 spacing
      // + 20 thread ID), which should fit comfortably into the static buffer.
      DCHECK_LE(buffer_offset, 45);
      static_assert(45 < kStackBufferSize,
                    "stack-allocated buffer may not fit the message header");
      DCHECK_LT(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.
        NOTREACHED();
        buffer_offset = buffer_size - 1;
      }

      // Add a newline if necessary.
      if (buffer[buffer_offset - 1] != '\n') {
        buffer[buffer_offset] = '\n';
        ++buffer_offset;
      }

      DCHECK_LE(buffer_offset, buffer_size);
      file_.WriteAtCurrentPos(buffer, buffer_offset);

      if (iteration != 0) {
        delete[] buffer;
      }
      break;
    }
  }

 private:
  base::File file_;
};

}  // namespace leveldb

#endif  // THIRD_PARTY_LEVELDATABASE_CHROMIUM_LOGGER_H_