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
53namespace WTF {
54
55static const size_t maxPathLength = 1024;
56
57static PrintStream* s_file;
58static uint64_t s_fileData[(sizeof(FilePrintStream) + 7) / 8];
59static uint64_t s_lockedFileData[(sizeof(LockedPrintStream) + 7) / 8];
60
61static 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
109static void initializeLogFile()
110{
111 static std::once_flag once;
112 std::call_once(
113 once,
114 [] {
115 initializeLogFileOnce();
116 });
117}
118
119void 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
169PrintStream& dataFile()
170{
171 initializeLogFile();
172 return *s_file;
173}
174
175void dataLogFV(const char* format, va_list argList)
176{
177 dataFile().vprintf(format, argList);
178}
179
180void dataLogF(const char* format, ...)
181{
182 va_list argList;
183 va_start(argList, format);
184 dataLogFV(format, argList);
185 va_end(argList);
186}
187
188void dataLogFString(const char* str)
189{
190 dataFile().printf("%s", str);
191}
192
193} // namespace WTF
194
195