| 1 | /* |
| 2 | * Copyright (C) 2012 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 const 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 | |