summaryrefslogtreecommitdiff
path: root/src/mongo/logger/console.cpp
blob: 24ca62e946763fae6588f1ee3781fb676a89b00a (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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
/*    Copyright 2013 10gen Inc.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

#include "mongo/platform/basic.h"

#include "mongo/logger/console.h"

#include <iostream>

#ifdef _WIN32
#include <io.h>
#endif

namespace mongo {
namespace {

    /*
     * Theory of operation:
     *
     * At process start, the loader initializes "consoleMutex" to NULL.  At some point during static
     * initialization, the static initialization process, running in the one and only extant thread,
     * allocates a new boost::mutex on the heap and assigns consoleMutex to point to it.  While
     * consoleMutex is still NULL, we know that there is only one thread extant, so it is safe to
     * skip locking the consoleMutex in the Console constructor.  Once the mutex is initialized,
     * users of Console can start acquiring it.
     */

    boost::mutex *consoleMutex = new boost::mutex;

#if defined(_WIN32)
    /**
     * Very basic implementation of a stream buffer backed by
     * a fixed sized internal buffer that flushes
     * to the console using WriteConsoleW().
     *
     * Implementation notes:
     * WriteConsoleW() vs std::wcout
     *     std::wcout produces garbage output for messages containing 2-byte sequences
     *     under all the terminal environments tested (Command Prompt, Power Shell, Cygwin).
     *     3-byte sequences will cause std::wcout to go into a bad state (failbit=true).
     *     Also tried calling _setmode( _fileno(stdout), ...) with various modes (_O_U16TEXT,
     *     ...), all of which produces garbage results on the console.
     */

    class ConsoleStreamBuffer : public std::streambuf {
    public:

        explicit ConsoleStreamBuffer(HANDLE consoleHandle) : _consoleHandle(consoleHandle) {
            // leave room at end of buffer for overflow character
            setp(&(_buffer[0]), &(_buffer[_bufferSize-1]));
        }

        int_type overflow(int_type ch) {
            if (ch == traits_type::eof()) {
                return ch;
            }

            // push to end of buffer
            *(pptr()) = ch;
            pbump(1);

            // If the overflow byte is part of a UTF-8 multi-byte sequence,
            // locate the beginning of the byte sequence and determine if
            // the byte sequence represents a complete unicode code point.
            // If the sequence is complete, proceed to flush the buffer.
            // If the sequence is incomplete, flush the buffer up to but
            // not including incomplete code point before re-inserting the
            // bytes back into the internal buffer.

            if (ch & 0x80) {
                // length of unicode byte sequence can be at most 4 in length. See RFC 3629
                int length = 0;

                // do not look back beyond 4 characters (maximum length for UTF-8 sequences)
                // p will point to first byte of unicode code point upon exit from loop
                char* sequenceBegin = pptr();
                for (int i = 0; i < 4; i++) {
                    length++;
                    sequenceBegin--;
                    // check for beginning of code point
                    if (*sequenceBegin & 0x40) {
                        break;
                    }
                    // check for invalid byte. all bytes in multi-byte sequence
                    // should have left most bit set
                    if (!(*sequenceBegin & 0x80)) {
                        break;
                    }
                }

                // get expected length of code point
                // cast to unsigned type to avoid sign extension
                int expectedLength = _bitsToSequenceLength[uint8_t(*sequenceBegin) >> 4];

                // if beginning of sequence was not found, expectedLength will be zero and
                // we will consider the multi-byte sequence to be garbage and flush the
                // entire buffer

                // if code point is incomplete, rewind and flush buffer before
                // refilling with incomplete byte sequence
                if (length < expectedLength)
                {
                    // rewind internal buffer to end before unfinished byte sequence
                    pbump(-length);

                    // store result. should eventually return
                    // original overflow character on success
                    int_type result = flushToConsole() ? ch : traits_type::eof();

                    // after flushing pptr() will point to beginning of buffer
                    // copy bytes starting from p to beginning of internal buffer
                    for (int i = 0; i < length; i++) {
                        *(pptr()) = *sequenceBegin;
                        pbump(1);
                        sequenceBegin++;
                    }

                    return result;
                }
            }

            return flushToConsole() ? ch : traits_type::eof();
        }

        int sync() {
            return flushToConsole() ? 0 : -1;
        }

    private:

        // keep this value reasonable. this class is used primarily
        // to buffer log messages
        static const size_t _bufferSize = 1024U;

        // mapping of leftmost 4 bits of first byte of code point
        // to number of expected bytes in complete code point
        // 110x -> 2
        // 1110 -> 3
        // 1111 -> 4
        static const int _bitsToSequenceLength[];

        // In the event that WriteConsoleW fails, return false
        // to allow stream to update error state flags (most likely badbit)

        bool flushToConsole() {
            std::ptrdiff_t n = pptr() - pbase();
            pbump(-n);

            // convert multi-byte buffer to wide characters and output using WriteConsoleW

            wchar_t bufferWide[_bufferSize];
            int length = MultiByteToWideChar(CP_UTF8, 0, _buffer, n, bufferWide, _bufferSize);
            const wchar_t* unwrittenBegin = bufferWide;
            int unwrittenCount = length; // m holds number of unwritten wide characters in buffer

            while (unwrittenCount > 0) {
                DWORD written;
                BOOL success = WriteConsoleW(_consoleHandle, unwrittenBegin, unwrittenCount,
                                             &written, NULL);
                if (!success) {
                    return false;
                }
                unwrittenCount -= written;
                unwrittenBegin += written;
            }
            return true;
        }

        HANDLE _consoleHandle;
        char _buffer[_bufferSize];
    };

// 0x0 - 0xb - invalid start of multi-byte sequence`

const int ConsoleStreamBuffer::_bitsToSequenceLength[] = {
    0, 0, 0, 0,
    0, 0, 0, 0,
    0, 0, 0, 0,
    2, 2, 3, 4 };

// Create a output stream to redirect console writes
// to WriteConsoleW() if there is a real console available (FILE_TYPE_CHAR)
// Otherwise, return std::cout
//
// Command Prompt and Power Shell (GetFileType() == FILE_TYPE_CHAR):
//     Set the font to either Lucida Console or Consolas to see 2 and 3-byte sequences.
//     The fonts provided by the existing Windows console programs do not
//     render complex 4-byte sequences propertly.
// File redirection (GetFileType() == FILE_TYPE_DISK):
//     When the output is redirected from the console, WriteConsoleW will fail and we
//     will fall back on using _write().
// Cygwin (GetFileType() == FILE_TYPE_PIPE):
//     An "invalid handle" error message will be displayed on first log message and
//     flushToConsole() will fall back on using _write().

std::ostream* getWindowsOutputStream() {
    HANDLE consoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);
    DWORD consoleType = GetFileType(consoleHandle);
    if (consoleType != FILE_TYPE_CHAR) {
        return &std::cout;
    }
    std::streambuf* windowsStreamBuffer = new ConsoleStreamBuffer(consoleHandle);
    std::ostream* windowsOutputStream = new std::ostream(windowsStreamBuffer);
    return windowsOutputStream;
}

std::ostream* windowsOutputStream = getWindowsOutputStream();
#endif // defined(_WIN32)

}  // namespace

    Console::Console() : _consoleLock() {
        if (consoleMutex) {
            boost::unique_lock<boost::mutex> lk(*consoleMutex);
            lk.swap(_consoleLock);
        }
    }

    std::ostream& Console::out() {
#if defined(_WIN32)
        // check value of ostream in case
        // static initializer has not been invoked
        if (windowsOutputStream) {
            return *windowsOutputStream;
        }
#endif // defined(_WIN32)
        return std::cout;
    }

}  // namespace mongo