/*
* The MIT License (MIT)
*
* Copyright (c) 2015 Almex
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
package be.raildelays.logging;
import be.raildelays.batch.bean.BatchExcelRow;
import be.raildelays.delays.Delays;
import be.raildelays.domain.entities.I18nEntity;
import be.raildelays.domain.entities.LineStop;
import be.raildelays.domain.entities.TrainLine;
import be.raildelays.domain.xls.ExcelRow;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Marker;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
/**
* This class typed logger is responsible to strictly format our output log ot match column width.
* <br/>
* Expected result:
* <pre>
* |=====|===================|======|==========|====|====|========|========|=====|=====|=====|=====|==|======|======|
* | | |000638| |0466| |Liège-Gu| |null | |null | |00|000639|000637|
* |=====|===================|======|==========|====|====|========|========|=====|=====|=====|=====|==|======|======|
* [Agg]: lacks_expected_time 0466 Liège-Gu null null
* [Agg]: candidate 0466 Liège-Gu 16:24 16:24
* [Agg]: lacks_expected_time 0466 Brussels null null
* [Agg]: candidate 0466 Brussels 16:24 16:24
* [Agg]: lacks_expected_time 0466 Brussels null null
* [Agg]: candidate 0466 Brussels 16:24 16:24
* [Agg]: after_processing 000638 11/07/2014 0466 Liège-Gu 16:24 16:24 000639 000637
* [Xls]: extracted_left 000636 11/07/2014 0466 Brussels 16:24 16:24 000637 000635
* [Xls]: extracted_left 000636 11/07/2014 0466 Brussels 16:24 16:24 000637 000635
* [Xls]: extracted_rigth null
* [Xls]: extracted_rigth null
* [Xls]: departure 000636 11/07/2014 0466 Brussels 16:24 16:24 000637 000635
* [Xls]: arrival 000636 11/07/2014 0466 Brussels 16:24 16:24 000637 000635
* [Nxt]: item 11/07/2014 0466 0466 Brussels Liège-Gu 16:24 16:24 16:24 16:24 00
* [Nxt]: candidates[0] 000084 11/07/2014 0514 Liège-Gu 17:00 17:05 000685 000683
* [Nxt]: candidates[1] 000569 11/07/2014 0515 Liège-Gu 18:00 18:05 000570 000568
* [Nxt]: found_faster 11/07/2014 0466 0514 Brussels Liège-Gu 16:24 16:01 16:24 17:00 36
* [Ftr]: stop_result null
* |=====|===================|======|==========|====|====|========|========|=====|=====|=====|=====|==|======|======|
* </pre>
*
* @author Almex
*/
public class RaildelaysLogger implements Logger {
private static final int STATION_LENGTH = 12;
private static final int MESSAGE_LENGTH = 20;
private static final int PREFIX_LENGTH = 3;
private static final String ID_FORMAT = "000000";
private static final String TRAIN_FORMAT = "0000";
private static final String DELAY_FORMAT = "00";
private static final String DATE_FORMAT = "dd/MM/yyyy";
private static final String TIME_FORMAT = "HH:mm";
private static final int TOTAL_LENGTH = ID_FORMAT.length() + DATE_FORMAT.length() + 2 * TRAIN_FORMAT.length() +
2 * STATION_LENGTH + 4 * TIME_FORMAT.length() + 2 * ID_FORMAT.length() + 12;
private org.slf4j.Logger delegate;
private Marker marker;
private char separator = ' ';
private String type;
private Delegator<LineStop> lineStopDelegator = new Delegator<LineStop>() {
@Override
public String logLine(String message, LineStop object) {
/**
* We revert here departure and arrival because what we want to show here it's a stop:
* - we reach the stop at the arrival time <-> we start our route at departure time
* - we leave the stop at the departure time <-> we stop our route at arrival time
*/
return new LogLineBuilder()
.message(message)
.id(object.getId())
.date(object.getDate())
.expectedTrain(getTrainId(object.getTrainLine()))
.departureStation(I18nEntity.getNotNullName(object.getStation()))
.expectedDepartureTime(object.getArrivalTime() != null ? object.getArrivalTime().getExpectedTime() : null)
.expectedArrivalTime(object.getDepartureTime() != null ? object.getDepartureTime().getExpectedTime() : null)
.effectiveDepartureTime(object.getArrivalTime() != null ? object.getArrivalTime().getEffectiveTime() : null)
.effectiveArrivalTime(object.getDepartureTime() != null ? object.getDepartureTime().getEffectiveTime() : null)
.canceledDeparture(object.isCanceledArrival())
.canceledArrival(object.isCanceledDeparture())
.idPrevious(object.getPrevious() != null ? object.getPrevious().getId() : null)
.idNext(object.getNext() != null ? object.getNext().getId() : null)
.build();
}
};
private Delegator<ExcelRow> excelRowDelegator = new Delegator<ExcelRow>() { // NOSONAR
@Override
public String logLine(String message, ExcelRow object) {
return new LogLineBuilder()
.message(message)
.id(object.getId())
.date(object.getDate())
.expectedTrain(getTrainId(object.getExpectedTrainLine1()))
.effectiveTrain(getTrainId(object.getEffectiveTrainLine1()))
.departureStation(I18nEntity.getNotNullName(object.getDepartureStation()))
.arrivalStation(I18nEntity.getNotNullName(object.getArrivalStation()))
.expectedDepartureTime(object.getExpectedDepartureTime())
.expectedArrivalTime(object.getExpectedArrivalTime())
.effectiveDepartureTime(object.getEffectiveDepartureTime())
.effectiveArrivalTime(object.getEffectiveArrivalTime())
.build();
}
};
public RaildelaysLogger(String type, org.slf4j.Logger delegate) {
this.type = type;
this.delegate = delegate;
}
private static Long getTrainId(TrainLine trainLine) {
Long result = null;
if (trainLine != null) {
try {
if (trainLine.getRouteId() != null) {
result = trainLine.getRouteId();
}
} catch (NumberFormatException e) {
result = 0L;
}
}
return result;
}
@Override
public void info(String message, LineStop lineStop) {
lineStopDelegator.log(message, Level.INFO, lineStop);
}
@Override
public void debug(String message, LineStop lineStop) {
lineStopDelegator.log(message, Level.DEBUG, lineStop);
}
@Override
public void trace(String message, LineStop lineStop) {
lineStopDelegator.log(message, Level.TRACE, lineStop);
}
@Override
public void info(String message, List<LineStop> lineStops) {
lineStopDelegator.log(message, Level.INFO, lineStops);
}
@Override
public void debug(String message, List<LineStop> lineStops) {
lineStopDelegator.log(message, Level.DEBUG, lineStops);
}
@Override
public void trace(String message, List<LineStop> lineStops) {
lineStopDelegator.log(message, Level.TRACE, lineStops);
}
@Override
public void info(String message, ExcelRow excelRow) {
excelRowDelegator.log(message, Level.INFO, excelRow);
}
@Override
public void debug(String message, ExcelRow excelRow) {
excelRowDelegator.log(message, Level.DEBUG, excelRow);
}
@Override
public void trace(String message, ExcelRow excelRow) {
excelRowDelegator.log(message, Level.TRACE, excelRow);
}
@Override
public void info(String message, BatchExcelRow excelRow) {
excelRowDelegator.log(message, Level.INFO, excelRow);
}
@Override
public void debug(String message, BatchExcelRow excelRow) {
excelRowDelegator.log(message, Level.DEBUG, excelRow);
}
@Override
public void trace(String message, BatchExcelRow excelRow) {
excelRowDelegator.log(message, Level.TRACE, excelRow);
}
public void setDelegate(org.slf4j.Logger logger) {
this.delegate = logger;
}
public void setSeparator(char separator) {
this.separator = separator;
}
@Override
public String getName() {
return null;
}
@Override
public boolean isTraceEnabled() {
return false;
}
@Override
public void trace(String msg) {
}
@Override
public void trace(String format, Object arg) {
}
@Override
public void trace(String format, Object arg1, Object arg2) {
}
@Override
public void trace(String format, Object... arguments) {
}
@Override
public void trace(String msg, Throwable t) {
}
@Override
public boolean isTraceEnabled(Marker marker) {
return false;
}
@Override
public void trace(Marker marker, String msg) {
}
@Override
public void trace(Marker marker, String format, Object arg) {
}
@Override
public void trace(Marker marker, String format, Object arg1, Object arg2) {
}
@Override
public void trace(Marker marker, String format, Object... argArray) {
}
@Override
public void trace(Marker marker, String msg, Throwable t) {
}
@Override
public boolean isDebugEnabled() {
return false;
}
@Override
public void debug(String msg) {
delegate.debug(msg);
}
@Override
public void debug(String format, Object arg) {
delegate.debug(format, arg);
}
@Override
public void debug(String format, Object arg1, Object arg2) {
delegate.debug(format, arg1, arg2);
}
@Override
public void debug(String format, Object... arguments) {
delegate.debug(format, arguments);
}
@Override
public void debug(String msg, Throwable t) {
delegate.debug(msg, t);
}
@Override
public boolean isDebugEnabled(Marker marker) {
return delegate.isDebugEnabled(marker);
}
@Override
public void debug(Marker marker, String msg) {
delegate.debug(marker, msg);
}
@Override
public void debug(Marker marker, String format, Object arg) {
delegate.debug(marker, format, arg);
}
@Override
public void debug(Marker marker, String format, Object arg1, Object arg2) {
delegate.debug(marker, format, arg1, arg2);
}
@Override
public void debug(Marker marker, String format, Object... arguments) {
delegate.debug(marker, format, arguments);
}
@Override
public void debug(Marker marker, String msg, Throwable t) {
delegate.debug(marker, msg, t);
}
@Override
public boolean isInfoEnabled() {
return delegate.isInfoEnabled();
}
@Override
public void info(String msg) {
delegate.info(msg);
}
@Override
public void info(String format, Object arg) {
delegate.info(format, arg);
}
@Override
public void info(String format, Object arg1, Object arg2) {
delegate.info(format, arg1, arg2);
}
@Override
public void info(String format, Object... arguments) {
delegate.info(format, arguments);
}
@Override
public void info(String msg, Throwable t) {
delegate.info(msg, t);
}
@Override
public boolean isInfoEnabled(Marker marker) {
return delegate.isInfoEnabled(marker);
}
@Override
public void info(Marker marker, String msg) {
delegate.info(marker, msg);
}
@Override
public void info(Marker marker, String format, Object arg) {
delegate.info(marker, format, arg);
}
@Override
public void info(Marker marker, String format, Object arg1, Object arg2) {
delegate.info(marker, format, arg1, arg2);
}
@Override
public void info(Marker marker, String format, Object... arguments) {
delegate.info(marker, format, arguments);
}
@Override
public void info(Marker marker, String msg, Throwable t) {
delegate.info(marker, msg, t);
}
@Override
public boolean isWarnEnabled() {
return delegate.isWarnEnabled();
}
@Override
public void warn(String msg) {
delegate.warn(msg);
}
@Override
public void warn(String format, Object arg) {
delegate.warn(format, arg);
}
@Override
public void warn(String format, Object... arguments) {
delegate.warn(format, arguments);
}
@Override
public void warn(String format, Object arg1, Object arg2) {
delegate.warn(format, arg1, arg2);
}
@Override
public void warn(String msg, Throwable t) {
delegate.warn(msg, t);
}
@Override
public boolean isWarnEnabled(Marker marker) {
return delegate.isWarnEnabled(marker);
}
@Override
public void warn(Marker marker, String msg) {
delegate.warn(marker, msg);
}
@Override
public void warn(Marker marker, String format, Object arg) {
delegate.warn(marker, format, arg);
}
@Override
public void warn(Marker marker, String format, Object arg1, Object arg2) {
delegate.warn(marker, format, arg1, arg2);
}
@Override
public void warn(Marker marker, String format, Object... arguments) {
delegate.warn(marker, format, arguments);
}
@Override
public void warn(Marker marker, String msg, Throwable t) {
delegate.warn(marker, msg, t);
}
@Override
public boolean isErrorEnabled() {
return delegate.isErrorEnabled();
}
@Override
public void error(String msg) {
delegate.error(msg);
}
@Override
public void error(String format, Object arg) {
delegate.error(format, arg);
}
@Override
public void error(String format, Object arg1, Object arg2) {
delegate.error(format, arg1, arg2);
}
@Override
public void error(String format, Object... arguments) {
delegate.error(format, arguments);
}
@Override
public void error(String msg, Throwable t) {
delegate.error(msg, t);
}
@Override
public boolean isErrorEnabled(Marker marker) {
return delegate.isErrorEnabled(marker);
}
@Override
public void error(Marker marker, String msg) {
delegate.error(marker, msg);
}
@Override
public void error(Marker marker, String format, Object arg) {
delegate.error(marker, format, arg);
}
@Override
public void error(Marker marker, String format, Object arg1, Object arg2) {
delegate.error(marker, format, arg1, arg2);
}
@Override
public void error(Marker marker, String format, Object... arguments) {
delegate.error(marker, format, arguments);
}
@Override
public void error(Marker marker, String msg, Throwable t) {
delegate.error(marker, msg, t);
}
private enum Level {DEBUG, TRACE, INFO}
private class LogLineBuilder {
private String message;
private Long id;
private LocalDate date;
private Long expectedTrain;
private Long effectiveTrain;
private String departureStation;
private String arrivalStation;
private LocalTime expectedDepartureTime;
private LocalTime expectedArrivalTime;
private LocalTime effectiveDepartureTime;
private LocalTime effectiveArrivalTime;
private Long idPrevious;
private Long idNext;
private boolean canceledDeparture;
private boolean canceledArrival;
public LogLineBuilder message(String message) {
this.message = message;
return this;
}
public LogLineBuilder id(Long id) {
this.id = id;
return this;
}
public LogLineBuilder idPrevious(Long idPrevious) {
this.idPrevious = idPrevious;
return this;
}
public LogLineBuilder idNext(Long idNext) {
this.idNext = idNext;
return this;
}
public LogLineBuilder date(LocalDate date) {
this.date = date;
return this;
}
public LogLineBuilder expectedTrain(Long expectedTrain) {
this.expectedTrain = expectedTrain;
return this;
}
public LogLineBuilder effectiveTrain(Long effectiveTrain) {
this.effectiveTrain = effectiveTrain;
return this;
}
public LogLineBuilder arrivalStation(String arrivalStation) {
this.arrivalStation = arrivalStation;
return this;
}
public LogLineBuilder departureStation(String departureStation) {
this.departureStation = departureStation;
return this;
}
public LogLineBuilder expectedDepartureTime(LocalTime expectedDepartureTime) {
this.expectedDepartureTime = expectedDepartureTime;
return this;
}
public LogLineBuilder expectedArrivalTime(LocalTime expectedArrivalTime) {
this.expectedArrivalTime = expectedArrivalTime;
return this;
}
public LogLineBuilder effectiveDepartureTime(LocalTime effectiveDepartureTime) {
this.effectiveDepartureTime = effectiveDepartureTime;
return this;
}
public LogLineBuilder effectiveArrivalTime(LocalTime effectiveArrivalTime) {
this.effectiveArrivalTime = effectiveArrivalTime;
return this;
}
public LogLineBuilder canceledDeparture(boolean canceled) {
this.canceledDeparture = canceled;
return this;
}
public LogLineBuilder canceledArrival(boolean canceled) {
this.canceledArrival = canceled;
return this;
}
public String build() {
final StringBuilder builder = new StringBuilder();
final NumberFormat idFormat = new DecimalFormat(ID_FORMAT);
final NumberFormat trainFormat = new DecimalFormat(TRAIN_FORMAT);
final NumberFormat delayFormat = new DecimalFormat(DELAY_FORMAT);
final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern(DATE_FORMAT);
final DateTimeFormatter timeFormat = DateTimeFormatter.ofPattern(TIME_FORMAT);
if (expectedTrain != null ||
departureStation != null ||
expectedDepartureTime != null ||
effectiveDepartureTime != null) {
builder.append(StringUtils.rightPad(id != null ? idFormat.format(id) : "null", ID_FORMAT.length()));
builder.append(separator);
builder.append(StringUtils.rightPad(date != null ? dateFormat.format(date) : "", DATE_FORMAT.length()));
builder.append(separator);
builder.append(StringUtils.rightPad(expectedTrain != null ? trainFormat.format(expectedTrain) : "", TRAIN_FORMAT.length()));
builder.append(separator);
builder.append(StringUtils.rightPad(effectiveTrain != null ? trainFormat.format(effectiveTrain) : "", TRAIN_FORMAT.length()));
builder.append(separator);
builder.append(StringUtils.rightPad(departureStation != null ? substringCenter(departureStation, STATION_LENGTH, '~') : "", STATION_LENGTH));
builder.append(separator);
builder.append(StringUtils.rightPad(arrivalStation != null ? substringCenter(arrivalStation, STATION_LENGTH, '~') : "", STATION_LENGTH));
builder.append(separator);
builder.append(StringUtils.rightPad(expectedDepartureTime != null ? timeFormat.format(expectedDepartureTime) : "null", TIME_FORMAT.length()));
builder.append(separator);
builder.append(StringUtils.rightPad(expectedArrivalTime != null ? timeFormat.format(expectedArrivalTime) : "null", TIME_FORMAT.length()));
builder.append(separator);
builder.append(formatEffectiveTime(effectiveDepartureTime, canceledDeparture));
builder.append(separator);
builder.append(formatEffectiveTime(effectiveArrivalTime, canceledArrival));
builder.append(separator);
builder.append(delayFormat.format(Delays.toMinutes(Delays.computeDelay(expectedArrivalTime, effectiveArrivalTime))));
builder.append(separator);
builder.append(StringUtils.rightPad(idPrevious != null ? idFormat.format(idPrevious) : "", ID_FORMAT.length()));
builder.append(separator);
builder.append(StringUtils.rightPad(idNext != null ? idFormat.format(idNext) : "", ID_FORMAT.length()));
} else {
builder.append(StringUtils.rightPad("null", TOTAL_LENGTH));
}
builder.append(separator);
return builder.toString();
}
public String formatEffectiveTime(LocalTime effectiveTime, boolean canceled) {
final DateTimeFormatter timeFormat = DateTimeFormatter.ofPattern(TIME_FORMAT);
final DateTimeFormatter canceledTimeFormat = DateTimeFormatter.ofPattern("HH'x'mm");
final String result;
if (effectiveTime != null) {
result = StringUtils.rightPad(effectiveTime.format(canceled ? canceledTimeFormat : timeFormat), TIME_FORMAT.length());
} else {
result = StringUtils.center(canceled ? "x" : "", TIME_FORMAT.length());
}
return result;
}
public String substringCenter(String characters, int length, char centerCharacter) {
String result = null;
if (characters != null) {
StringBuilder builder = new StringBuilder(length);
if (characters.length() <= length) {
result = characters;
} else {
builder.append(characters.substring(0, length - 4));
builder.append(centerCharacter);
builder.append(characters.substring(characters.length() - 3));
result = builder.toString();
}
}
return result;
}
}
private abstract class Delegator<T> {
public abstract String logLine(String message, T object);
public void log(String message, Level level, T object) {
final StringBuilder builder = new StringBuilder();
builder.append(separator);
builder.append(StringUtils.rightPad(type != null ? "[" + StringUtils.substring(type, 0, PREFIX_LENGTH) + "]" : "", PREFIX_LENGTH + 2));
builder.append(separator);
builder.append(StringUtils.rightPad(message != null ? StringUtils.substring(message, 0, MESSAGE_LENGTH) : "", MESSAGE_LENGTH));
builder.append(separator);
if (object != null) {
builder.append(logLine(message, object));
} else {
builder.append(StringUtils.rightPad("null", TOTAL_LENGTH));
}
switch (level) {
case DEBUG:
delegate.debug(builder.toString());
break;
case INFO:
delegate.info(builder.toString());
break;
case TRACE:
delegate.trace(builder.toString());
break;
default:
delegate.error(" [NO_LOG_LEVEL] " + builder.toString());
}
}
public void log(String message, Level level, List<? extends T> objects) {
if (objects != null) {
for (int i = 0; i < objects.size(); i++) {
T object = objects.get(i);
log(message + "[" + i + "]", level, object);
}
}
}
}
}