1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 #include <log4cxx/logstring.h>
19 #include <log4cxx/fileappender.h>
20 #include <log4cxx/helpers/stringhelper.h>
21 #include <log4cxx/helpers/loglog.h>
22 #include <log4cxx/helpers/optionconverter.h>
23 #include <log4cxx/helpers/synchronized.h>
24 #include <log4cxx/helpers/pool.h>
25 #include <log4cxx/helpers/fileoutputstream.h>
26 #include <log4cxx/helpers/outputstreamwriter.h>
27 #include <log4cxx/helpers/bufferedwriter.h>
28 #include <log4cxx/helpers/bytebuffer.h>
29 #include <log4cxx/helpers/synchronized.h>
30
31 using namespace log4cxx;
32 using namespace log4cxx::helpers;
33 using namespace log4cxx::spi;
34
IMPLEMENT_LOG4CXX_OBJECT(FileAppender)35 IMPLEMENT_LOG4CXX_OBJECT(FileAppender)
36
37
38 FileAppender::FileAppender()
39 {
40 LOCK_W sync(mutex);
41 fileAppend = true;
42 bufferedIO = false;
43 bufferSize = 8 * 1024;
44 }
45
FileAppender(const LayoutPtr & layout1,const LogString & fileName1,bool append1,bool bufferedIO1,int bufferSize1)46 FileAppender::FileAppender(const LayoutPtr& layout1, const LogString& fileName1,
47 bool append1, bool bufferedIO1, int bufferSize1)
48 : WriterAppender(layout1)
49 {
50 {
51 LOCK_W sync(mutex);
52 fileAppend = append1;
53 fileName = fileName1;
54 bufferedIO = bufferedIO1;
55 bufferSize = bufferSize1;
56 }
57 Pool p;
58 activateOptions(p);
59 }
60
FileAppender(const LayoutPtr & layout1,const LogString & fileName1,bool append1)61 FileAppender::FileAppender(const LayoutPtr& layout1, const LogString& fileName1,
62 bool append1)
63 : WriterAppender(layout1)
64 {
65 {
66 LOCK_W sync(mutex);
67 fileAppend = append1;
68 fileName = fileName1;
69 bufferedIO = false;
70 bufferSize = 8 * 1024;
71 }
72 Pool p;
73 activateOptions(p);
74 }
75
FileAppender(const LayoutPtr & layout1,const LogString & fileName1)76 FileAppender::FileAppender(const LayoutPtr& layout1, const LogString& fileName1)
77 : WriterAppender(layout1)
78 {
79 {
80 LOCK_W sync(mutex);
81 fileAppend = true;
82 fileName = fileName1;
83 bufferedIO = false;
84 bufferSize = 8 * 1024;
85 }
86 Pool p;
87 activateOptions(p);
88 }
89
~FileAppender()90 FileAppender::~FileAppender()
91 {
92 finalize();
93 }
94
setAppend(bool fileAppend1)95 void FileAppender::setAppend(bool fileAppend1)
96 {
97 LOCK_W sync(mutex);
98 this->fileAppend = fileAppend1;
99 }
100
setFile(const LogString & file)101 void FileAppender::setFile(const LogString& file)
102 {
103 LOCK_W sync(mutex);
104 fileName = file;
105 }
106
107
108
setBufferedIO(bool bufferedIO1)109 void FileAppender::setBufferedIO(bool bufferedIO1)
110 {
111 LOCK_W sync(mutex);
112 this->bufferedIO = bufferedIO1;
113
114 if (bufferedIO1)
115 {
116 setImmediateFlush(false);
117 }
118 }
119
setOption(const LogString & option,const LogString & value)120 void FileAppender::setOption(const LogString& option,
121 const LogString& value)
122 {
123 if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("FILE"), LOG4CXX_STR("file"))
124 || StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("FILENAME"), LOG4CXX_STR("filename")))
125 {
126 LOCK_W sync(mutex);
127 fileName = stripDuplicateBackslashes(value);
128 }
129 else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("APPEND"), LOG4CXX_STR("append")))
130 {
131 LOCK_W sync(mutex);
132 fileAppend = OptionConverter::toBoolean(value, true);
133 }
134 else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("BUFFEREDIO"), LOG4CXX_STR("bufferedio")))
135 {
136 LOCK_W sync(mutex);
137 bufferedIO = OptionConverter::toBoolean(value, true);
138 }
139 else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("IMMEDIATEFLUSH"), LOG4CXX_STR("immediateflush")))
140 {
141 LOCK_W sync(mutex);
142 bufferedIO = !OptionConverter::toBoolean(value, false);
143 }
144 else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("BUFFERSIZE"), LOG4CXX_STR("buffersize")))
145 {
146 LOCK_W sync(mutex);
147 bufferSize = OptionConverter::toFileSize(value, 8 * 1024);
148 }
149 else
150 {
151 WriterAppender::setOption(option, value);
152 }
153 }
154
activateOptions(Pool & p)155 void FileAppender::activateOptions(Pool& p)
156 {
157 LOCK_W sync(mutex);
158 int errors = 0;
159
160 if (!fileName.empty())
161 {
162 try
163 {
164 setFile(fileName, fileAppend, bufferedIO, bufferSize, p);
165 }
166 catch (IOException& e)
167 {
168 errors++;
169 LogString msg(LOG4CXX_STR("setFile("));
170 msg.append(fileName);
171 msg.append(1, (logchar) 0x2C /* ',' */);
172 StringHelper::toString(fileAppend, msg);
173 msg.append(LOG4CXX_STR(") call failed."));
174 errorHandler->error(msg, e, ErrorCode::FILE_OPEN_FAILURE);
175 }
176 }
177 else
178 {
179 errors++;
180 LogLog::error(LogString(LOG4CXX_STR("File option not set for appender ["))
181 + name + LOG4CXX_STR("]."));
182 LogLog::warn(LOG4CXX_STR("Are you using FileAppender instead of ConsoleAppender?"));
183 }
184
185 if (errors == 0)
186 {
187 WriterAppender::activateOptions(p);
188 }
189 }
190
191
192 /**
193 * Replaces double backslashes (except the leading doubles of UNC's)
194 * with single backslashes for compatibility with existing path
195 * specifications that were working around use of
196 * OptionConverter::convertSpecialChars in XML configuration files.
197 *
198 * @param src source string
199 * @return modified string
200 *
201 *
202 */
stripDuplicateBackslashes(const LogString & src)203 LogString FileAppender::stripDuplicateBackslashes(const LogString& src)
204 {
205 logchar backslash = 0x5C; // '\\'
206 LogString::size_type i = src.find_last_of(backslash);
207
208 if (i != LogString::npos)
209 {
210 LogString tmp(src);
211
212 for (;
213 i != LogString::npos && i > 0;
214 i = tmp.find_last_of(backslash, i - 1))
215 {
216 //
217 // if the preceding character is a slash then
218 // remove the preceding character
219 // and continue processing
220 if (tmp[i - 1] == backslash)
221 {
222 tmp.erase(i, 1);
223 i--;
224
225 if (i == 0)
226 {
227 break;
228 }
229 }
230 else
231 {
232 //
233 // if there an odd number of slashes
234 // the string wasn't trying to work around
235 // OptionConverter::convertSpecialChars
236 return src;
237 }
238 }
239
240 return tmp;
241 }
242
243 return src;
244 }
245
246 /**
247 <p>Sets and <i>opens</i> the file where the log output will
248 go. The specified file must be writable.
249
250 <p>If there was already an opened file, then the previous file
251 is closed first.
252
253 <p><b>Do not use this method directly. To configure a FileAppender
254 or one of its subclasses, set its properties one by one and then
255 call activateOptions.</b>
256
257 @param filename The path to the log file.
258 @param append If true will append to fileName. Otherwise will
259 truncate fileName.
260 @param bufferedIO
261 @param bufferSize
262
263 @throws IOException
264
265 */
setFile(const LogString & filename,bool append1,bool bufferedIO1,size_t bufferSize1,Pool & p)266 void FileAppender::setFile(
267 const LogString& filename,
268 bool append1,
269 bool bufferedIO1,
270 size_t bufferSize1,
271 Pool& p)
272 {
273 LOCK_W sync(mutex);
274
275 // It does not make sense to have immediate flush and bufferedIO.
276 if (bufferedIO1)
277 {
278 setImmediateFlush(false);
279 }
280
281 closeWriter();
282
283 bool writeBOM = false;
284
285 if (StringHelper::equalsIgnoreCase(getEncoding(),
286 LOG4CXX_STR("utf-16"), LOG4CXX_STR("UTF-16")))
287 {
288 //
289 // don't want to write a byte order mark if the file exists
290 //
291 if (append1)
292 {
293 File outFile;
294 outFile.setPath(filename);
295 writeBOM = !outFile.exists(p);
296 }
297 else
298 {
299 writeBOM = true;
300 }
301 }
302
303 OutputStreamPtr outStream;
304
305 try
306 {
307 outStream = new FileOutputStream(filename, append1);
308 }
309 catch (IOException&)
310 {
311 LogString parentName = File().setPath(filename).getParent(p);
312
313 if (!parentName.empty())
314 {
315 File parentDir;
316 parentDir.setPath(parentName);
317
318 if (!parentDir.exists(p) && parentDir.mkdirs(p))
319 {
320 outStream = new FileOutputStream(filename, append1);
321 }
322 else
323 {
324 throw;
325 }
326 }
327 else
328 {
329 throw;
330 }
331 }
332
333
334 //
335 // if a new file and UTF-16, then write a BOM
336 //
337 if (writeBOM)
338 {
339 char bom[] = { (char) 0xFE, (char) 0xFF };
340 ByteBuffer buf(bom, 2);
341 outStream->write(buf, p);
342 }
343
344 WriterPtr newWriter(createWriter(outStream));
345
346 if (bufferedIO1)
347 {
348 newWriter = new BufferedWriter(newWriter, bufferSize1);
349 }
350
351 setWriter(newWriter);
352
353 this->fileAppend = append1;
354 this->bufferedIO = bufferedIO1;
355 this->fileName = filename;
356 this->bufferSize = (int)bufferSize1;
357 writeHeader(p);
358
359 }
360
361