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