package org.eclipse.osgi.internal.log;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.StandardCharsets;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import org.eclipse.core.runtime.adaptor.EclipseStarter;
import org.eclipse.equinox.log.ExtendedLogEntry;
import org.eclipse.equinox.log.LogFilter;
import org.eclipse.equinox.log.SynchronousLogListener;
import org.eclipse.osgi.framework.log.FrameworkLogEntry;
import org.eclipse.osgi.internal.framework.EquinoxConfiguration;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleException;
import org.osgi.service.log.LogEntry;
import org.osgi.service.log.LogLevel;
import org.osgi.service.log.LogService;
import org.osgi.service.log.admin.LoggerAdmin;
import org.osgi.service.log.admin.LoggerContext;
class EquinoxLogWriter implements SynchronousLogListener, LogFilter {
private static final String PASSWORD = "-password";
private static final String SESSION = "!SESSION";
private static final String ENTRY = "!ENTRY";
private static final String SUBENTRY = "!SUBENTRY";
private static final String MESSAGE = "!MESSAGE";
private static final String STACK = "!STACK";
private static final String LINE_SEPARATOR;
static {
String s = System.getProperty("line.separator");
LINE_SEPARATOR = s == null ? "\n" : s;
}
private static final int DEFAULT_LOG_SIZE = 1000;
private static final int DEFAULT_LOG_FILES = 10;
private static final int LOG_SIZE_MIN = 10;
private static final String PROP_LOG_LEVEL = "eclipse.log.level";
private static final String PROP_LOG_SIZE_MAX = "eclipse.log.size.max";
private static final String PROP_LOG_FILE_MAX = "eclipse.log.backup.max";
private static final String LOG_EXT = ".log";
private static final String BACKUP_MARK = ".bak_";
private static final String PROP_LOG_INCLUDE_COMMAND_LINE = "eclipse.log.include.commandline";
private boolean consoleLog = false;
private boolean newSession = true;
private File outFile;
private Writer writer;
private final String loggerName;
private final boolean enabled;
private final EquinoxConfiguration environmentInfo;
int maxLogSize = DEFAULT_LOG_SIZE;
int maxLogFiles = DEFAULT_LOG_FILES;
int backupIdx = 0;
private int logLevel = FrameworkLogEntry.OK;
private boolean includeCommandLine = true;
private LoggerAdmin loggerAdmin = null;
public EquinoxLogWriter(File outFile, String loggerName, boolean enabled, EquinoxConfiguration environmentInfo) {
this.outFile = outFile;
this.writer = null;
this.loggerName = loggerName;
this.enabled = enabled;
this.environmentInfo = environmentInfo;
readLogProperties();
}
public EquinoxLogWriter(Writer writer, String loggerName, boolean enabled, EquinoxConfiguration environmentInfo) {
if (writer == null)
this.writer = logForErrorStream();
else
this.writer = writer;
this.loggerName = loggerName;
this.enabled = enabled;
this.environmentInfo = environmentInfo;
}
private Throwable getRoot(Throwable t) {
Throwable root = null;
if (t instanceof BundleException)
root = ((BundleException) t).getNestedException();
if (t instanceof InvocationTargetException)
root = ((InvocationTargetException) t).getTargetException();
if (root instanceof InvocationTargetException || root instanceof BundleException) {
Throwable deeplyNested = getRoot(root);
if (deeplyNested != null)
root = deeplyNested;
}
return root;
}
private void writeArgs(String header, String[] args) throws IOException {
if (args == null || args.length == 0)
return;
write(header);
for (int i = 0; i < args.length; i++) {
if (i > 0 && PASSWORD.equals(args[i - 1]))
write(" (omitted)");
else
write(" " + args[i]);
}
writeln();
}
private String getSessionTimestamp() {
String ts = environmentInfo.getConfiguration("eclipse.startTime");
if (ts != null) {
try {
return getDate(new Date(Long.parseLong(ts)));
} catch (NumberFormatException e) {
}
}
return getDate(new Date());
}
private void writeSession() throws IOException {
write(SESSION);
writeSpace();
String date = getSessionTimestamp();
write(date);
writeSpace();
for (int i = SESSION.length() + date.length(); i < 78; i++) {
write("-");
}
writeln();
try {
String key = "eclipse.buildId";
String value = environmentInfo.getConfiguration(key, "unknown");
writeln(key + "=" + value);
key = "java.fullversion";
value = System.getProperty(key);
if (value == null) {
key = "java.version";
value = System.getProperty(key);
writeln(key + "=" + value);
key = "java.vendor";
value = System.getProperty(key);
writeln(key + "=" + value);
} else {
writeln(key + "=" + value);
}
} catch (Exception e) {
}
write("BootLoader constants: OS=" + environmentInfo.getOS());
write(", ARCH=" + environmentInfo.getOSArch());
write(", WS=" + environmentInfo.getWS());
writeln(", NL=" + environmentInfo.getNL());
if (includeCommandLine) {
writeArgs("Framework arguments: ", environmentInfo.getNonFrameworkArgs());
writeArgs("Command-line arguments: ", environmentInfo.getCommandLineArgs());
}
}
public void close() {
try {
if (writer != null) {
Writer tmpWriter = writer;
writer = null;
tmpWriter.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void openFile() {
if (writer == null) {
if (outFile != null) {
try {
writer = logForStream(ExtendedLogServiceFactory.secureAction.getFileOutputStream(outFile, true));
} catch (IOException e) {
writer = logForErrorStream();
}
} else {
writer = logForErrorStream();
}
}
}
private void closeFile() {
if (outFile != null) {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
writer = null;
}
}
}
private synchronized void log(FrameworkLogEntry logEntry) {
if (logEntry == null)
return;
if (!isLoggable(logEntry.getSeverity()))
return;
try {
checkLogFileSize();
openFile();
if (newSession) {
writeSession();
newSession = false;
}
writeLog(0, logEntry);
writer.flush();
} catch (Exception e) {
System.err.println("An exception occurred while writing to the platform log:");
e.printStackTrace(System.err);
System.err.println("Logging to the console instead.");
try {
writer = logForErrorStream();
writeLog(0, logEntry);
writer.flush();
} catch (Exception e2) {
System.err.println("An exception occurred while logging to the console:");
e2.printStackTrace(System.err);
}
} finally {
closeFile();
}
}
public synchronized void setWriter(Writer newWriter, boolean append) {
setOutput(null, newWriter, append);
}
public synchronized void setFile(File newFile, boolean append) throws IOException {
if (newFile != null && !newFile.equals(this.outFile)) {
readLogProperties();
backupIdx = 0;
}
setOutput(newFile, null, append);
environmentInfo.setConfiguration(EclipseStarter.PROP_LOGFILE, newFile == null ? "" : newFile.getAbsolutePath());
}
synchronized void setLoggerAdmin(LoggerAdmin loggerAdmin) {
this.loggerAdmin = loggerAdmin;
applyLogLevel();
}
public synchronized File getFile() {
return outFile;
}
public void setConsoleLog(boolean consoleLog) {
this.consoleLog = consoleLog;
}
private void setOutput(File newOutFile, Writer newWriter, boolean append) {
if (newOutFile == null || !newOutFile.equals(this.outFile)) {
if (this.writer != null) {
try {
this.writer.close();
} catch (IOException e) {
e.printStackTrace();
}
this.writer = null;
}
File oldOutFile = this.outFile;
this.outFile = newOutFile;
this.writer = newWriter;
boolean copyFailed = false;
if (append && oldOutFile != null && oldOutFile.isFile()) {
Reader fileIn = null;
try {
openFile();
fileIn = new InputStreamReader(ExtendedLogServiceFactory.secureAction.getFileInputStream(oldOutFile), "UTF-8");
copyReader(fileIn, this.writer);
} catch (IOException e) {
copyFailed = true;
e.printStackTrace();
} finally {
if (fileIn != null) {
try {
fileIn.close();
} catch (IOException e) {
e.printStackTrace();
}
if (!copyFailed)
oldOutFile.delete();
}
closeFile();
}
}
}
}
private void copyReader(Reader reader, Writer aWriter) throws IOException {
char buffer[] = new char[1024];
int count;
while ((count = reader.read(buffer, 0, buffer.length)) > 0) {
aWriter.write(buffer, 0, count);
}
}
private String getDate(Date date) {
Calendar c = Calendar.getInstance();
c.setTime(date);
StringBuilder sb = new StringBuilder();
appendPaddedInt(c.get(Calendar.YEAR), 4, sb).append('-');
appendPaddedInt(c.get(Calendar.MONTH) + 1, 2, sb).append('-');
appendPaddedInt(c.get(Calendar.DAY_OF_MONTH), 2, sb).append(' ');
appendPaddedInt(c.get(Calendar.HOUR_OF_DAY), 2, sb).append(':');
appendPaddedInt(c.get(Calendar.MINUTE), 2, sb).append(':');
appendPaddedInt(c.get(Calendar.SECOND), 2, sb).append('.');
appendPaddedInt(c.get(Calendar.MILLISECOND), 3, sb);
return sb.toString();
}
private StringBuilder appendPaddedInt(int value, int pad, StringBuilder buffer) {
pad = pad - 1;
if (pad == 0)
return buffer.append(Integer.toString(value));
int padding = (int) Math.pow(10, pad);
if (value >= padding)
return buffer.append(Integer.toString(value));
while (padding > value && padding > 1) {
buffer.append('0');
padding = padding / 10;
}
buffer.append(value);
return buffer;
}
private String getStackTrace(Throwable t) {
if (t == null)
return null;
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
t.printStackTrace(pw);
Throwable root = getRoot(t);
if (root != null) {
pw.println("Root exception:");
root.printStackTrace(pw);
}
return sw.toString();
}
private Writer logForStream(OutputStream output) {
return new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8));
}
private Writer logForErrorStream() {
return new BufferedWriter(new OutputStreamWriter(System.err));
}
private void writeLog(int depth, FrameworkLogEntry entry) throws IOException {
writeEntry(depth, entry);
writeMessage(entry);
writeStack(entry);
FrameworkLogEntry[] children = entry.getChildren();
if (children != null) {
for (FrameworkLogEntry child : children) {
writeLog(depth + 1, child);
}
}
}
private void writeEntry(int depth, FrameworkLogEntry entry) throws IOException {
if (depth == 0) {
writeln();
write(ENTRY);
} else {
write(SUBENTRY);
writeSpace();
write(Integer.toString(depth));
}
writeSpace();
write(entry.getEntry());
writeSpace();
write(Integer.toString(entry.getSeverity()));
writeSpace();
write(Integer.toString(entry.getBundleCode()));
writeSpace();
write(getDate(new Date()));
writeln();
}
private void writeMessage(FrameworkLogEntry entry) throws IOException {
write(MESSAGE);
writeSpace();
writeln(entry.getMessage());
}
private void writeStack(FrameworkLogEntry entry) throws IOException {
Throwable t = entry.getThrowable();
if (t != null) {
String stack = getStackTrace(t);
write(STACK);
writeSpace();
write(Integer.toString(entry.getStackCode()));
writeln();
write(stack);
}
}
private void write(String message) throws IOException {
if (message != null) {
writer.write(message);
if (consoleLog)
System.out.print(message);
}
}
private void writeln(String s) throws IOException {
write(s);
writeln();
}
private void writeln() throws IOException {
write(LINE_SEPARATOR);
}
private void writeSpace() throws IOException {
write(" ");
}
private boolean checkLogFileSize() {
if (maxLogSize == 0)
return true;
boolean isBackupOK = true;
if (outFile != null) {
if ((ExtendedLogServiceFactory.secureAction.length(outFile) >> 10) > maxLogSize) {
String logFilename = outFile.getAbsolutePath();
String backupFilename = "";
if (logFilename.toLowerCase().endsWith(LOG_EXT)) {
backupFilename = logFilename.substring(0, logFilename.length() - LOG_EXT.length()) + BACKUP_MARK + backupIdx + LOG_EXT;
} else {
backupFilename = logFilename + BACKUP_MARK + backupIdx;
}
File backupFile = new File(backupFilename);
if (backupFile.exists()) {
if (!backupFile.delete()) {
System.err.println("Error when trying to delete old log file: " + backupFile.getName());
if (backupFile.renameTo(new File(backupFile.getAbsolutePath() + System.currentTimeMillis()))) {
System.err.println("So we rename it to filename: " + backupFile.getName());
} else {
System.err.println("And we also cannot rename it!");
isBackupOK = false;
}
}
}
boolean isRenameOK = outFile.renameTo(backupFile);
if (!isRenameOK) {
System.err.println("Error when trying to rename log file to backup one.");
isBackupOK = false;
}
File newFile = new File(logFilename);
setOutput(newFile, null, false);
openFile();
try {
writeSession();
writeln();
writeln("This is a continuation of log file " + backupFile.getAbsolutePath());
writeln("Created Time: " + getDate(new Date(System.currentTimeMillis())));
writer.flush();
} catch (IOException ioe) {
ioe.printStackTrace(System.err);
}
closeFile();
backupIdx = (++backupIdx) % maxLogFiles;
}
}
return isBackupOK;
}
private void readLogProperties() {
String newMaxLogSize = environmentInfo.getConfiguration(PROP_LOG_SIZE_MAX);
if (newMaxLogSize != null) {
maxLogSize = Integer.parseInt(newMaxLogSize);
if (maxLogSize != 0 && maxLogSize < LOG_SIZE_MIN) {
maxLogSize = LOG_SIZE_MIN;
}
}
String newMaxLogFiles = environmentInfo.getConfiguration(PROP_LOG_FILE_MAX);
if (newMaxLogFiles != null) {
maxLogFiles = Integer.parseInt(newMaxLogFiles);
if (maxLogFiles < 1) {
maxLogFiles = DEFAULT_LOG_FILES;
}
}
String newLogLevel = environmentInfo.getConfiguration(PROP_LOG_LEVEL);
if (newLogLevel != null) {
if (newLogLevel.equals("ERROR"))
logLevel = FrameworkLogEntry.ERROR;
else if (newLogLevel.equals("WARNING"))
logLevel = FrameworkLogEntry.ERROR | FrameworkLogEntry.WARNING;
else if (newLogLevel.equals("INFO"))
logLevel = FrameworkLogEntry.INFO | FrameworkLogEntry.ERROR | FrameworkLogEntry.WARNING | FrameworkLogEntry.CANCEL;
else
logLevel = FrameworkLogEntry.OK;
}
includeCommandLine = "true".equals(environmentInfo.getConfiguration(PROP_LOG_INCLUDE_COMMAND_LINE, "true"));
applyLogLevel();
}
void applyLogLevel() {
if (loggerAdmin == null) {
return;
}
LoggerContext rootContext = loggerAdmin.getLoggerContext(null);
Map<String, LogLevel> rootLevels = rootContext.getLogLevels();
rootLevels.put(loggerName, getLogLevel());
rootContext.setLogLevels(rootLevels);
}
private LogLevel getLogLevel() {
if (logLevel == 0) {
return LogLevel.TRACE;
}
if ((logLevel & FrameworkLogEntry.INFO) != 0) {
return LogLevel.INFO;
}
if ((logLevel & FrameworkLogEntry.WARNING) != 0) {
return LogLevel.WARN;
}
if ((logLevel & FrameworkLogEntry.ERROR) != 0) {
return LogLevel.ERROR;
}
return LogLevel.AUDIT;
}
private boolean isLoggable(int fwkEntrySeverity) {
if (logLevel == 0)
return true;
return (fwkEntrySeverity & logLevel) != 0;
}
@SuppressWarnings("deprecation")
@Override
public boolean isLoggable(Bundle bundle, String loggableName, int loggableLevel) {
if (!enabled)
return false;
if (loggerName.equals(loggableName))
return isLoggable(convertSeverity(loggableLevel));
if (EquinoxLogServices.PERF_LOGGER_NAME.equals(loggableName))
return false;
if (!EquinoxLogServices.EQUINOX_LOGGER_NAME.equals(loggerName))
return false;
return loggableLevel == LogService.LOG_ERROR;
}
@SuppressWarnings("deprecation")
@Override
public void logged(LogEntry entry) {
if (!(entry instanceof ExtendedLogEntry))
return;
ExtendedLogEntry extended = (ExtendedLogEntry) entry;
Object context = extended.getContext();
if (context instanceof FrameworkLogEntry) {
log((FrameworkLogEntry) context);
return;
}
log(new FrameworkLogEntry(getFwkEntryTag(entry), convertSeverity(entry.getLevel()), 0, entry.getMessage(), 0, entry.getException(), null));
}
private static String getFwkEntryTag(LogEntry entry) {
Bundle b = entry.getBundle();
if (b != null && b.getSymbolicName() != null)
return b.getSymbolicName();
return "unknown";
}
@SuppressWarnings("deprecation")
private static int convertSeverity(int entryLevel) {
switch (entryLevel) {
case LogService.LOG_ERROR :
return FrameworkLogEntry.ERROR;
case LogService.LOG_WARNING :
return FrameworkLogEntry.WARNING;
case LogService.LOG_INFO :
return FrameworkLogEntry.INFO;
case LogService.LOG_DEBUG :
return FrameworkLogEntry.OK;
default :
return 32;
}
}
public String getLoggerName() {
return loggerName;
}
}