/* ************************************************************************
#
# DivConq
#
# http://divconq.com/
#
# Copyright:
# Copyright 2014 eTimeline, LLC. All rights reserved.
#
# License:
# See the license.txt file in the project's top-level directory for details.
#
# Authors:
# * Andy White
#
************************************************************************ */
package divconq.lang.op;
import java.io.BufferedWriter;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormat;
import divconq.lang.StringBuilder32;
import divconq.log.DebugLevel;
import divconq.struct.FieldStruct;
import divconq.struct.ListStruct;
import divconq.struct.RecordStruct;
import divconq.struct.Struct;
import divconq.util.StringUtil;
import divconq.work.TaskRun;
public class OperationLogger extends OperationObserver implements IOperationLogger {
protected ListStruct entries = new ListStruct();
protected ListStruct replaces = new ListStruct();
protected Path logfile = null;
protected BufferedWriter bw = null;
public void setLogFile(Path v) {
this.logfile = v;
this.setField("LogFile", v);
}
public OperationLogger() {
this.setField("Entries", this.entries);
this.setField("Replaces", this.replaces);
}
public void addReplace(String oldValue, String newValue) {
this.replaces.addItem(
new RecordStruct(
new FieldStruct("Old", oldValue),
new FieldStruct("New", newValue)
)
);
}
@Override
public Struct deepCopy() {
OperationLogger cp = new OperationLogger();
this.doCopy(cp);
return cp;
}
@Override
public String logToString() {
StringBuilder32 sb = new StringBuilder32();
for (Struct s : this.entries.getItems()) {
String line = this.formatLogEntry((RecordStruct)s);
if (StringUtil.isNotEmpty(line))
sb.append(line + "\n");
}
return sb.toString();
}
public String formatMessage(String msg) {
for (Struct s : this.replaces.getItems()) {
RecordStruct re = (RecordStruct) s;
msg = msg.replace(re.getFieldAsString("Old"), re.getFieldAsString("New"));
}
return msg;
}
public String formatLogEntry(RecordStruct entry) {
DateTime occured = entry.getFieldAsDateTime("Occurred");
String lvl = entry.getFieldAsString("Level");
lvl = StringUtil.alignLeft(lvl, ' ', 6);
String msg = this.formatMessage(entry.getFieldAsString("Message"));
// return null if msg was filtered
if (StringUtil.isEmpty(msg))
return null;
return occured + " " + lvl + msg;
}
@Override
public void log(OperationContext ctx, RecordStruct entry) {
String lvl = entry.getFieldAsString("Level");
// only log the correct level, unlike context which keeps them all
if (ctx.getLevel().getCode() < DebugLevel.valueOf(lvl).getCode())
return;
this.entries.addItem(entry);
if (this.bw == null)
return;
try {
String line = this.formatLogEntry((RecordStruct)entry);
if (StringUtil.isNotEmpty(line)) {
this.bw.append(line);
this.bw.newLine();
this.bw.flush();
}
}
catch (Exception x) {
// life is bad if we get here, don't think we should do anything
// but let the system melt down
}
}
@Override
public void start(OperationContext ctx) {
// super class might already set logfile, but if not see if we need to
if ((this.logfile == null) && !this.isFieldEmpty("LogFile"))
this.logfile = Paths.get(this.getFieldAsString("LogFile"));
TaskRun run = ctx.getTaskRun();
RecordStruct params = (run != null) ? run.getTask().getParams() : null;
// if temp folder is set then we will log into there
String tempfolder = ((params != null) && !params.isFieldEmpty("_TempFolder"))
? params.getFieldAsString("_TempFolder")
: null;
if ((this.logfile == null) && StringUtil.isNotEmpty(tempfolder)) {
String fname = !this.isFieldEmpty("LogFileName")
? this.getFieldAsString("LogFileName")
: DateTimeFormat.forPattern("yyyyMMdd'_'HHmmss").print(new DateTime(DateTimeZone.UTC)) + ".log";
this.logfile = Paths.get(tempfolder, fname);
}
// if we do have a log file try to open it
if (this.logfile != null) {
try {
Files.createDirectories(this.logfile.getParent());
this.bw = Files.newBufferedWriter(this.logfile, Charset.forName("UTF-8"));
}
catch (Exception x) {
ctx.errorTr(181, x);
}
}
// automatically clean references to the temp folder in log file
if (StringUtil.isNotEmpty(tempfolder)) {
this.addReplace(tempfolder, "");
this.addReplace(tempfolder.replace('\\', '/'), "");
}
}
@Override
public void stop(OperationContext ctx) {
if (this.bw == null)
return;
try {
this.bw.flush();
this.bw.close();
String fn = this.logfile.getFileName().toString();
// automatically rename .tmp to .log
if (fn.endsWith(".tmp")) {
fn = fn.substring(0, fn.length() - 4) + ".log";
Path finallog = this.logfile.resolveSibling(fn);
// if there happens to be a log already there, replace it
Files.move(this.logfile, finallog, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
this.logfile = finallog;
}
}
catch (Exception x) {
// don't care, nothing we can do
System.out.println("Error copying log file: " + x);
}
}
}