blob: 8d678d4f95732685bb9bb9cdae604950a8771278 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <log4cxx/logstring.h>
#include <log4cxx/fileappender.h>
#include <log4cxx/helpers/stringhelper.h>
#include <log4cxx/helpers/loglog.h>
#include <log4cxx/helpers/optionconverter.h>
#include <log4cxx/helpers/synchronized.h>
#include <log4cxx/helpers/pool.h>
#include <log4cxx/helpers/fileoutputstream.h>
#include <log4cxx/helpers/outputstreamwriter.h>
#include <log4cxx/helpers/bufferedwriter.h>
#include <log4cxx/helpers/bytebuffer.h>
#include <log4cxx/helpers/synchronized.h>
using namespace log4cxx;
using namespace log4cxx::helpers;
using namespace log4cxx::spi;
IMPLEMENT_LOG4CXX_OBJECT(FileAppender)
FileAppender::FileAppender()
{
LOCK_W sync(mutex);
fileAppend = true;
bufferedIO = false;
bufferSize = 8 * 1024;
}
FileAppender::FileAppender(const LayoutPtr& layout1, const LogString& fileName1,
bool append1, bool bufferedIO1, int bufferSize1)
: WriterAppender(layout1)
{
{
LOCK_W sync(mutex);
fileAppend = append1;
fileName = fileName1;
bufferedIO = bufferedIO1;
bufferSize = bufferSize1;
}
Pool p;
activateOptions(p);
}
FileAppender::FileAppender(const LayoutPtr& layout1, const LogString& fileName1,
bool append1)
: WriterAppender(layout1)
{
{
LOCK_W sync(mutex);
fileAppend = append1;
fileName = fileName1;
bufferedIO = false;
bufferSize = 8 * 1024;
}
Pool p;
activateOptions(p);
}
FileAppender::FileAppender(const LayoutPtr& layout1, const LogString& fileName1)
: WriterAppender(layout1)
{
{
LOCK_W sync(mutex);
fileAppend = true;
fileName = fileName1;
bufferedIO = false;
bufferSize = 8 * 1024;
}
Pool p;
activateOptions(p);
}
FileAppender::~FileAppender()
{
finalize();
}
void FileAppender::setAppend(bool fileAppend1)
{
LOCK_W sync(mutex);
this->fileAppend = fileAppend1;
}
void FileAppender::setFile(const LogString& file)
{
LOCK_W sync(mutex);
fileName = file;
}
void FileAppender::setBufferedIO(bool bufferedIO1)
{
LOCK_W sync(mutex);
this->bufferedIO = bufferedIO1;
if (bufferedIO1)
{
setImmediateFlush(false);
}
}
void FileAppender::setOption(const LogString& option,
const LogString& value)
{
if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("FILE"), LOG4CXX_STR("file"))
|| StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("FILENAME"), LOG4CXX_STR("filename")))
{
LOCK_W sync(mutex);
fileName = stripDuplicateBackslashes(value);
}
else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("APPEND"), LOG4CXX_STR("append")))
{
LOCK_W sync(mutex);
fileAppend = OptionConverter::toBoolean(value, true);
}
else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("BUFFEREDIO"), LOG4CXX_STR("bufferedio")))
{
LOCK_W sync(mutex);
bufferedIO = OptionConverter::toBoolean(value, true);
}
else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("IMMEDIATEFLUSH"), LOG4CXX_STR("immediateflush")))
{
LOCK_W sync(mutex);
bufferedIO = !OptionConverter::toBoolean(value, false);
}
else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("BUFFERSIZE"), LOG4CXX_STR("buffersize")))
{
LOCK_W sync(mutex);
bufferSize = OptionConverter::toFileSize(value, 8 * 1024);
}
else
{
WriterAppender::setOption(option, value);
}
}
void FileAppender::activateOptions(Pool& p)
{
LOCK_W sync(mutex);
int errors = 0;
if (!fileName.empty())
{
try
{
setFile(fileName, fileAppend, bufferedIO, bufferSize, p);
}
catch (IOException& e)
{
errors++;
LogString msg(LOG4CXX_STR("setFile("));
msg.append(fileName);
msg.append(1, (logchar) 0x2C /* ',' */);
StringHelper::toString(fileAppend, msg);
msg.append(LOG4CXX_STR(") call failed."));
errorHandler->error(msg, e, ErrorCode::FILE_OPEN_FAILURE);
}
}
else
{
errors++;
LogLog::error(LogString(LOG4CXX_STR("File option not set for appender ["))
+ name + LOG4CXX_STR("]."));
LogLog::warn(LOG4CXX_STR("Are you using FileAppender instead of ConsoleAppender?"));
}
if (errors == 0)
{
WriterAppender::activateOptions(p);
}
}
/**
* Replaces double backslashes (except the leading doubles of UNC's)
* with single backslashes for compatibility with existing path
* specifications that were working around use of
* OptionConverter::convertSpecialChars in XML configuration files.
*
* @param src source string
* @return modified string
*
*
*/
LogString FileAppender::stripDuplicateBackslashes(const LogString& src)
{
logchar backslash = 0x5C; // '\\'
LogString::size_type i = src.find_last_of(backslash);
if (i != LogString::npos)
{
LogString tmp(src);
for (;
i != LogString::npos && i > 0;
i = tmp.find_last_of(backslash, i - 1))
{
//
// if the preceding character is a slash then
// remove the preceding character
// and continue processing
if (tmp[i - 1] == backslash)
{
tmp.erase(i, 1);
i--;
if (i == 0)
{
break;
}
}
else
{
//
// if there an odd number of slashes
// the string wasn't trying to work around
// OptionConverter::convertSpecialChars
return src;
}
}
return tmp;
}
return src;
}
/**
<p>Sets and <i>opens</i> the file where the log output will
go. The specified file must be writable.
<p>If there was already an opened file, then the previous file
is closed first.
<p><b>Do not use this method directly. To configure a FileAppender
or one of its subclasses, set its properties one by one and then
call activateOptions.</b>
@param filename The path to the log file.
@param append If true will append to fileName. Otherwise will
truncate fileName.
@param bufferedIO
@param bufferSize
@throws IOException
*/
void FileAppender::setFile(
const LogString& filename,
bool append1,
bool bufferedIO1,
size_t bufferSize1,
Pool& p)
{
LOCK_W sync(mutex);
// It does not make sense to have immediate flush and bufferedIO.
if (bufferedIO1)
{
setImmediateFlush(false);
}
closeWriter();
bool writeBOM = false;
if (StringHelper::equalsIgnoreCase(getEncoding(),
LOG4CXX_STR("utf-16"), LOG4CXX_STR("UTF-16")))
{
//
// don't want to write a byte order mark if the file exists
//
if (append1)
{
File outFile;
outFile.setPath(filename);
writeBOM = !outFile.exists(p);
}
else
{
writeBOM = true;
}
}
OutputStreamPtr outStream;
try
{
outStream = new FileOutputStream(filename, append1);
}
catch (IOException&)
{
LogString parentName = File().setPath(filename).getParent(p);
if (!parentName.empty())
{
File parentDir;
parentDir.setPath(parentName);
if (!parentDir.exists(p) && parentDir.mkdirs(p))
{
outStream = new FileOutputStream(filename, append1);
}
else
{
throw;
}
}
else
{
throw;
}
}
//
// if a new file and UTF-16, then write a BOM
//
if (writeBOM)
{
char bom[] = { (char) 0xFE, (char) 0xFF };
ByteBuffer buf(bom, 2);
outStream->write(buf, p);
}
WriterPtr newWriter(createWriter(outStream));
if (bufferedIO1)
{
newWriter = new BufferedWriter(newWriter, bufferSize1);
}
setWriter(newWriter);
this->fileAppend = append1;
this->bufferedIO = bufferedIO1;
this->fileName = filename;
this->bufferSize = (int)bufferSize1;
writeHeader(p);
}