1 | /* |
2 | * Copyright (C) 2012-2019 Apple Inc. All rights reserved. |
3 | * |
4 | * Redistribution and use in source and binary forms, with or without |
5 | * modification, are permitted provided that the following conditions |
6 | * are met: |
7 | * 1. Redistributions of source code must retain the above copyright |
8 | * notice, this list of conditions and the following disclaimer. |
9 | * 2. Redistributions in binary form must reproduce the above copyright |
10 | * notice, this list of conditions and the following disclaimer in the |
11 | * documentation and/or other materials provided with the distribution. |
12 | * |
13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
14 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
20 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
21 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
23 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
24 | */ |
25 | |
26 | #include "config.h" |
27 | #include <wtf/DataLog.h> |
28 | |
29 | #include <stdarg.h> |
30 | #include <string.h> |
31 | #include <wtf/FilePrintStream.h> |
32 | #include <wtf/LockedPrintStream.h> |
33 | #include <wtf/ProcessID.h> |
34 | #include <wtf/Threading.h> |
35 | #include <mutex> |
36 | #include <thread> |
37 | |
38 | #if OS(UNIX) || OS(DARWIN) |
39 | #include <unistd.h> |
40 | #endif |
41 | |
42 | #define DATA_LOG_TO_FILE 0 |
43 | |
44 | // Set to 1 to use the temp directory from confstr instead of hardcoded directory. |
45 | // The last component of DATA_LOG_FILENAME will still be used. |
46 | #define DATA_LOG_TO_DARWIN_TEMP_DIR 0 |
47 | |
48 | // Uncomment to force logging to the given file regardless of what the environment variable says. |
49 | // Note that we will append ".<pid>.txt" where <pid> is the PID. |
50 | // This path won't work on Windows, make sure to change to something like C:\\Users\\<more path>\\log.txt. |
51 | #define DATA_LOG_FILENAME "/tmp/WTFLog" |
52 | |
53 | namespace WTF { |
54 | |
55 | static constexpr size_t maxPathLength = 1024; |
56 | |
57 | static PrintStream* s_file; |
58 | static uint64_t s_fileData[(sizeof(FilePrintStream) + 7) / 8]; |
59 | static uint64_t s_lockedFileData[(sizeof(LockedPrintStream) + 7) / 8]; |
60 | |
61 | static void initializeLogFileOnce() |
62 | { |
63 | const char* filename = nullptr; |
64 | |
65 | if (s_file) |
66 | return; |
67 | |
68 | #if DATA_LOG_TO_FILE |
69 | #if DATA_LOG_TO_DARWIN_TEMP_DIR |
70 | char filenameBuffer[maxPathLength + 1]; |
71 | #if defined(DATA_LOG_FILENAME) |
72 | const char* logBasename = strrchr(DATA_LOG_FILENAME, '/'); |
73 | if (!logBasename) |
74 | logBasename = (char*)DATA_LOG_FILENAME; |
75 | #else |
76 | const char* logBasename = "WTFLog" ; |
77 | #endif |
78 | |
79 | bool success = confstr(_CS_DARWIN_USER_TEMP_DIR, filenameBuffer, sizeof(filenameBuffer)); |
80 | if (success) { |
81 | // FIXME: Assert that the path ends with a slash instead of adding a slash if it does not exist |
82 | // once <rdar://problem/23579077> is fixed in all iOS Simulator versions that we use. |
83 | size_t lastComponentLength = strlen(logBasename) + 20; // More than enough for ".<pid>.txt" |
84 | size_t dirnameLength = strlen(filenameBuffer); |
85 | bool shouldAddPathSeparator = filenameBuffer[dirnameLength - 1] != '/' && logBasename[0] != '/'; |
86 | if (lastComponentLength + shouldAddPathSeparator <= sizeof(filenameBuffer) - dirnameLength - 1) { |
87 | if (shouldAddPathSeparator) |
88 | strncat(filenameBuffer, "/" , 1); |
89 | strncat(filenameBuffer, logBasename, sizeof(filenameBuffer) - strlen(filenameBuffer) - 1); |
90 | filename = filenameBuffer; |
91 | } |
92 | } |
93 | #elif defined(DATA_LOG_FILENAME) |
94 | filename = DATA_LOG_FILENAME; |
95 | #else |
96 | filename = getenv("WTF_DATA_LOG_FILENAME" ); |
97 | #endif |
98 | char actualFilename[maxPathLength + 1]; |
99 | |
100 | if (filename && !strstr(filename, "%pid" )) { |
101 | snprintf(actualFilename, sizeof(actualFilename), "%s.%%pid.txt" , filename); |
102 | filename = actualFilename; |
103 | } |
104 | #endif // DATA_LOG_TO_FILE |
105 | |
106 | setDataFile(filename); |
107 | } |
108 | |
109 | static void initializeLogFile() |
110 | { |
111 | static std::once_flag once; |
112 | std::call_once( |
113 | once, |
114 | [] { |
115 | initializeLogFileOnce(); |
116 | }); |
117 | } |
118 | |
119 | void setDataFile(const char* path) |
120 | { |
121 | FilePrintStream* file = nullptr; |
122 | char formattedPath[maxPathLength + 1]; |
123 | const char* pathToOpen = path; |
124 | |
125 | if (path) { |
126 | const char* pidFormat = strstr(path, "%pid" ); |
127 | if (pidFormat) { |
128 | size_t leadingPathLength = pidFormat - path; |
129 | size_t pathCharactersAvailable = std::min(maxPathLength, leadingPathLength); |
130 | strncpy(formattedPath, path, pathCharactersAvailable); |
131 | char* nextDest = formattedPath + pathCharactersAvailable; |
132 | pathCharactersAvailable = maxPathLength - pathCharactersAvailable; |
133 | if (pathCharactersAvailable) { |
134 | int pidTextLength = snprintf(nextDest, pathCharactersAvailable, "%d" , getCurrentProcessID()); |
135 | |
136 | if (pidTextLength < 0 || static_cast<size_t>(pidTextLength) >= pathCharactersAvailable) |
137 | pathCharactersAvailable = 0; |
138 | else { |
139 | pathCharactersAvailable -= static_cast<size_t>(pidTextLength); |
140 | nextDest += pidTextLength; |
141 | strncpy(nextDest, pidFormat + 4, pathCharactersAvailable); |
142 | } |
143 | } |
144 | formattedPath[maxPathLength] = '\0'; |
145 | pathToOpen = formattedPath; |
146 | } |
147 | |
148 | file = FilePrintStream::open(pathToOpen, "w" ).release(); |
149 | if (file) |
150 | WTFLogAlways("*** DataLog output to \"%s\" ***\n" , pathToOpen); |
151 | else |
152 | WTFLogAlways("Warning: Could not open DataLog file %s for writing.\n" , pathToOpen); |
153 | } |
154 | |
155 | if (!file) { |
156 | // Use placement new; this makes it easier to use dataLog() to debug |
157 | // fastMalloc. |
158 | file = new (s_fileData) FilePrintStream(stderr, FilePrintStream::Borrow); |
159 | } |
160 | |
161 | setvbuf(file->file(), 0, _IONBF, 0); // Prefer unbuffered output, so that we get a full log upon crash or deadlock. |
162 | |
163 | if (s_file) |
164 | s_file->flush(); |
165 | |
166 | s_file = new (s_lockedFileData) LockedPrintStream(std::unique_ptr<FilePrintStream>(file)); |
167 | } |
168 | |
169 | PrintStream& dataFile() |
170 | { |
171 | initializeLogFile(); |
172 | return *s_file; |
173 | } |
174 | |
175 | void dataLogFV(const char* format, va_list argList) |
176 | { |
177 | dataFile().vprintf(format, argList); |
178 | } |
179 | |
180 | void dataLogF(const char* format, ...) |
181 | { |
182 | va_list argList; |
183 | va_start(argList, format); |
184 | dataLogFV(format, argList); |
185 | va_end(argList); |
186 | } |
187 | |
188 | void dataLogFString(const char* str) |
189 | { |
190 | dataFile().printf("%s" , str); |
191 | } |
192 | |
193 | } // namespace WTF |
194 | |
195 | |