/*
* Funambol is a mobile platform developed by Funambol, Inc.
* Copyright (C) 2008 Funambol, Inc.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by
* the Free Software Foundation with the addition of the following permission
* added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
* WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE
* WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program; if not, see http://www.gnu.org/licenses or write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA.
*
* You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite
* 305, Redwood City, CA 94063, USA, or at email address info@funambol.com.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License
* version 3, these Appropriate Legal Notices must retain the display of the
* "Powered by Funambol" logo. If the display of the logo is not reasonably
* feasible for technical reasons, the Appropriate Legal Notices must display
* the words "Powered by Funambol".
*/
package com.funambol.util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;
import com.funambol.platform.FileAdapter;
/**
* This appender logs messages to a file using JSR75 (FileConnection)
* The appender tracks the file size and if it exceeds a given maximum size
* (customizable by clients) then the current log file is renamed appending a
* .old to the log name and a new one is created. Therefore the maximum size
* on this is about 2 times the maxFileSize (this is not accurate as there is
* no limit on the size of the single message printed).
*/
public class FileAppender implements Appender {
private String allLogFileName ="allsynclog.txt";
private String path = "file:///root1/";
private String contentPath = path;
private String fileUrl = "file:///root1/synclog.txt";
private String fileName = "synclog.txt";
private String oldSuffix = ".sav.txt";
private FileAdapter file;
private OutputStream os;
private long maxFileSize = 512 * 1024;
private boolean generateContentInMemory = false;
private Object lock = new Object();
/**
* Default constructor
*/
public FileAppender(String path, String fileName) {
if (path != null && fileName != null) {
if (path.endsWith("/")) {
this.fileUrl = path + fileName;
this.path = path;
} else {
this.fileUrl = path + "/" + fileName;
this.path = path + "/";
}
this.fileName = fileName;
// By default the contentPath is the same as the path
contentPath = this.path;
}
os = null;
}
//----------------------------------------------------------- Public Methods
/**
* Sets the maximum file size. Once this is size is reached, the current log
* file is renamed and a new one is created. This way we have at most 2 log
* files whose size is (roughly) bound to maxFileSize.
* The minimum file size is 1024 as smaller size does not really make sense.
* If a client needs smaller files it should probably the usage of other
* Appenders or modify the behavior of this one by deriving it.
*
* @param maxFileSize the max size in bytes
*/
public void setMaxFileSize(long maxFileSize) {
if (maxFileSize > 1024) {
this.maxFileSize = maxFileSize;
}
}
/**
* Sets the content path. This path is the directory where the combined log
* is placed so that the LogContent is accessible. By default this directory
* is the same as the log directory, but it is possible to specify a
* different one if needed.
*/
public void setContentPath(String path) {
contentPath = path;
}
/**
* Sets the content type of the log when it is retrieved via getLogContent.
* By default the FileAppender returns a content in a file, but if the
* client prefers an inlined value, then this method allows to force this
* behavior.
* Note that regardless of this setting, the log is always written to a
* file.
*/
public void setLogContentType(boolean memory) {
generateContentInMemory = memory;
}
/**
* FileAppender writes one message to the output file
*/
public void writeLogMessage(String level, String msg) {
synchronized(lock) {
try {
if (os != null) {
Date now = new Date();
StringBuffer logMsg = new StringBuffer(now.toString());
logMsg.append(" [").append(level).append("] ");
logMsg.append(msg);
logMsg.append("\r\n");
os.write(logMsg.toString().getBytes());
os.flush();
// If the file grows beyond the limit, we rename it and create a new
// one
if (file.getSize() > maxFileSize) {
try {
String oldFileName = fileUrl + oldSuffix;
FileAdapter oldFile = new FileAdapter(oldFileName);
if (oldFile.exists()) {
oldFile.delete();
}
file.rename(oldFileName);
file.close();
// Reopen the file
initLogFile();
} catch (Exception ioe) {
System.out.println("Exception while renaming " + ioe);
}
}
}
} catch (Exception e) {
System.out.println("Exception while logging. " + e);
e.printStackTrace();
// We try to close and reopen the log file. The message being logged
// is lost. We don't try to reopen it and get into an infinite
// recursion.
try {
file.close();
} catch (Exception e1) {
// We cannot even close the file, too bad. Logging maybe disabled
// at this point. Nevertheless we try to reopen the file
} finally {
initLogFile();
}
}
}
}
/**
* Init the logger
*/
public void initLogFile() {
synchronized(lock) {
try {
file = new FileAdapter(fileUrl);
os = file.openOutputStream(true);
} catch (Exception e) {
System.out.println("Cannot open or create file at: " + fileUrl);
e.printStackTrace();
}
}
}
/**
* FileAppender doesn't implement this method
*/
public void openLogFile() {
}
/**
* Close connection and streams
*/
public void closeLogFile() {
synchronized(lock) {
try {
if (os != null) {
os.close();
}
if (file != null) {
file.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* Perform additional actions needed when setting a new level.
* FileAppender doesn't implement this method
*/
public void setLogLevel(int level) {
}
/**
* Delete the log file
*/
public void deleteLogFile() {
synchronized(lock) {
try {
FileAdapter file = new FileAdapter(fileUrl);
if (file.exists()) {
file.delete();
}
} catch (Exception e) {
// We cannot log here, so just print to stdout
System.out.println("Cannot open or create file at: " + fileUrl);
e.printStackTrace();
}
}
}
public LogContent getLogContent() throws IOException {
synchronized (lock) {
String inlinedContent = null;
try {
//Merge txt sav log file
FileAdapter txtSavFa = null;
InputStream txtSavIs = null;
try {
txtSavFa = new FileAdapter(fileUrl + oldSuffix);
txtSavIs = txtSavFa.openInputStream();
} catch (Exception e) {
// We cannot log here, so just print to stdout
System.out.println("Sav file not found or not accessible");
}
// Prepare the output stream
FileAdapter allLogFa = null;
OutputStream allOs;
if (generateContentInMemory) {
allOs = new ByteArrayOutputStream();
} else {
allLogFa = new FileAdapter(contentPath + allLogFileName);
// Open in truncate mode
allOs = allLogFa.openOutputStream();
}
if (txtSavIs != null) {
merge(txtSavIs, allOs);
txtSavIs.close();
txtSavFa.close();
}
//Merge current log file. We need to close the file to be able
//to read it. Note that this method is synchronized on so so that we
//can safely do it
os.close();
file.close();
FileAdapter file = new FileAdapter(fileUrl);
InputStream txtIs = file.openInputStream();
merge(txtIs, allOs);
txtIs.close();
file.close();
// Now close the combined content file
if (generateContentInMemory) {
inlinedContent = allOs.toString();
} else {
allOs.close();
allLogFa.close();
}
} catch (Exception e) {
// We cannot log here, so just print to stdout
System.out.println("Cannot prepare log content:" + e.toString());
throw new IOException("Cannot prepare log content");
} finally {
initLogFile();
}
if (generateContentInMemory) {
return new LogContent(LogContent.STRING_CONTENT, inlinedContent);
} else {
return new LogContent(LogContent.FILE_CONTENT, path + allLogFileName);
}
}
}
private void merge(InputStream is, OutputStream os) throws IOException {
byte[] buffer = new byte[4096];
int length = 0;
do {
length = is.read(buffer);
if (length > 0) {
os.write(buffer, 0, length);
os.flush();
}
} while(length > 0);
}
}