/*
* Copyright © 2014 Cask Data, Inc.
*
* Licensed 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.
*/
package co.cask.cdap.logging.serialize;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.IThrowableProxy;
import ch.qos.logback.classic.spi.LoggerContextVO;
import ch.qos.logback.classic.spi.ThrowableProxyVO;
import co.cask.cdap.common.logging.LoggingContext;
import com.google.common.collect.Maps;
import org.apache.avro.Schema;
import org.apache.avro.generic.GenericArray;
import org.apache.avro.generic.GenericData;
import org.apache.avro.generic.GenericRecord;
import org.apache.avro.reflect.Nullable;
import org.slf4j.Marker;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import static co.cask.cdap.common.logging.LoggingContext.SystemTag;
import static co.cask.cdap.logging.serialize.Util.stringOrNull;
/**
* Class used to serialize/de-serialize ILoggingEvent.
*/
public final class LoggingEvent implements ILoggingEvent {
private static final int MAX_MDC_TAGS = 12;
private static final String MDC_NULL_KEY = ".null";
private String threadName;
private int level;
private String message;
@Nullable
private String[] argumentArray;
@Nullable
private String formattedMessage;
@Nullable
private String loggerName;
@Nullable
private LoggerContextVO loggerContextVO;
@Nullable
private IThrowableProxy throwableProxy;
@Nullable
private StackTraceElement[] callerData;
private boolean hasCallerData;
@Nullable
private Marker marker;
@Nullable
private Map<String, String> mdc;
private long timestamp;
private LoggingEvent() {}
public LoggingEvent(ILoggingEvent loggingEvent) {
this.threadName = loggingEvent.getThreadName();
this.level = loggingEvent.getLevel() == null ? Level.ERROR_INT : loggingEvent.getLevel().toInt();
this.message = loggingEvent.getMessage();
if (loggingEvent.getArgumentArray() != null) {
this.argumentArray = new String[loggingEvent.getArgumentArray().length];
int i = 0;
for (Object obj : loggingEvent.getArgumentArray()) {
this.argumentArray[i++] = obj == null ? null : obj.toString();
}
}
this.formattedMessage = loggingEvent.getFormattedMessage();
this.loggerName = loggingEvent.getLoggerName();
this.loggerContextVO = loggingEvent.getLoggerContextVO();
this.throwableProxy = ThrowableProxyVO.build(loggingEvent.getThrowableProxy());
if (loggingEvent.hasCallerData()) {
this.callerData = loggingEvent.getCallerData();
}
this.hasCallerData = loggingEvent.hasCallerData();
this.marker = loggingEvent.getMarker();
this.mdc = loggingEvent.getMDCPropertyMap();
this.timestamp = loggingEvent.getTimeStamp();
}
@Override
public String getThreadName() {
return threadName;
}
@Override
public Level getLevel() {
return Level.toLevel(level);
}
@Override
public String getMessage() {
return message;
}
@Override
public Object[] getArgumentArray() {
return argumentArray;
}
@Override
public String getFormattedMessage() {
return formattedMessage;
}
@Override
public String getLoggerName() {
return loggerName;
}
@Override
public LoggerContextVO getLoggerContextVO() {
return loggerContextVO;
}
@Override
public IThrowableProxy getThrowableProxy() {
return throwableProxy;
}
@Override
public StackTraceElement[] getCallerData() {
return callerData;
}
@Override
public boolean hasCallerData() {
return hasCallerData;
}
@Override
public Marker getMarker() {
return marker;
}
@Override
public Map<String, String> getMDCPropertyMap() {
return mdc;
}
@Override
public Map<String, String> getMdc() {
return mdc;
}
@Override
public long getTimeStamp() {
return timestamp;
}
@Override
public void prepareForDeferredProcessing() {
// Nothing to do!
}
public static GenericRecord encode(Schema schema, ILoggingEvent event, LoggingContext loggingContext) {
event.prepareForDeferredProcessing();
LoggingEvent loggingEvent = new LoggingEvent(event);
GenericRecord datum = new GenericData.Record(schema);
datum.put("threadName", loggingEvent.threadName);
datum.put("level", loggingEvent.level);
datum.put("message", loggingEvent.message);
if (loggingEvent.argumentArray != null) {
GenericArray<String> argArray =
new GenericData.Array<>(loggingEvent.argumentArray.length,
schema.getField("argumentArray").schema().getTypes().get(1));
Collections.addAll(argArray, loggingEvent.argumentArray);
datum.put("argumentArray", argArray);
}
datum.put("formattedMessage", loggingEvent.formattedMessage);
datum.put("loggerName", loggingEvent.loggerName);
datum.put("loggerContextVO", LoggerContextSerializer.encode(schema.getField("loggerContextVO").schema(),
loggingEvent.loggerContextVO));
datum.put("throwableProxy", ThrowableProxySerializer.encode(schema.getField("throwableProxy").schema(),
loggingEvent.throwableProxy));
if (loggingEvent.hasCallerData) {
datum.put("callerData", CallerDataSerializer.encode(schema.getField("callerData").schema(),
loggingEvent.callerData));
}
datum.put("hasCallerData", loggingEvent.hasCallerData);
//datum.put("marker", marker);
datum.put("mdc", generateContextMdc(loggingContext, loggingEvent.getMDCPropertyMap()));
datum.put("timestamp", loggingEvent.timestamp);
return datum;
}
static Map<String, String> generateContextMdc(LoggingContext loggingContext, Map<String, String> mdc) {
if (loggingContext == null) {
throw new IllegalStateException(String.format("Logging context not setup correctly for MDC %s", mdc));
}
Map<String, String> contextMdcMap = encodeMdcMap(mdc);
Map<String, SystemTag> systemTagMap = loggingContext.getSystemTagsMap();
for (Map.Entry<String, SystemTag> entry : systemTagMap.entrySet()) {
contextMdcMap.put(entry.getKey(), entry.getValue().getValue());
}
return contextMdcMap;
}
static Map<String, String> encodeMdcMap(Map<String, String> mdc) {
Map<String, String> encodeMap = Maps.newHashMapWithExpectedSize(MAX_MDC_TAGS * 2);
int i = 0;
for (Map.Entry<String, String> entry : mdc.entrySet()) {
if (i++ > MAX_MDC_TAGS) {
break;
}
// Any tag beginning with . is reserved
if (entry.getKey() == null || !entry.getKey().startsWith(".")) {
// AVRO does not allow null map keys.
encodeMap.put(entry.getKey() == null ? MDC_NULL_KEY : entry.getKey(), entry.getValue());
}
}
return encodeMap;
}
@SuppressWarnings("unchecked")
public static ILoggingEvent decode(GenericRecord datum) {
LoggingEvent loggingEvent = new LoggingEvent();
loggingEvent.threadName = stringOrNull(datum.get("threadName"));
loggingEvent.level = (Integer) datum.get("level");
loggingEvent.message = stringOrNull(datum.get("message"));
GenericArray<?> argArray = (GenericArray<?>) datum.get("argumentArray");
if (argArray != null) {
loggingEvent.argumentArray = new String[argArray.size()];
for (int i = 0; i < argArray.size(); ++i) {
loggingEvent.argumentArray[i] = argArray.get(i) == null ? null : argArray.get(i).toString();
}
}
loggingEvent.formattedMessage = stringOrNull(datum.get("formattedMessage"));
loggingEvent.loggerName = stringOrNull(datum.get("loggerName"));
loggingEvent.loggerContextVO = LoggerContextSerializer.decode((GenericRecord) datum.get("loggerContextVO"));
loggingEvent.throwableProxy = ThrowableProxySerializer.decode((GenericRecord) datum.get("throwableProxy"));
loggingEvent.callerData = CallerDataSerializer.decode((GenericArray<GenericRecord>) datum.get("callerData"));
loggingEvent.hasCallerData = (Boolean) datum.get("hasCallerData");
loggingEvent.mdc = decodeMdcMap((Map<?, ?>) datum.get("mdc"));
loggingEvent.timestamp = (Long) datum.get("timestamp");
return loggingEvent;
}
static Map<String, String> decodeMdcMap(Map<?, ?> map) {
if (map == null) {
return null;
}
Map<String, String> stringMap = Maps.newHashMapWithExpectedSize(map.size());
for (Map.Entry<?, ?> entry : map.entrySet()) {
// AVRO does not allow null map keys.
stringMap.put(entry.getKey() == null || entry.getKey().toString().equals(MDC_NULL_KEY) ?
null : entry.getKey().toString(),
entry.getValue() == null ? null : entry.getValue().toString());
}
return stringMap;
}
@Override
public String toString() {
return "LoggingEvent{" +
"timestamp=" + timestamp +
", formattedMessage='" + formattedMessage + '\'' +
", threadName='" + threadName + '\'' +
", level=" + Level.toLevel(level) +
", message='" + message + '\'' +
", argumentArray=" + (argumentArray == null ? null : Arrays.asList(argumentArray)) +
", formattedMessage='" + formattedMessage + '\'' +
", loggerName='" + loggerName + '\'' +
", loggerContextVO=" + loggerContextVO +
", throwableProxy=" + throwableProxy +
", callerData=" + (callerData == null ? null : Arrays.asList(callerData)) +
", hasCallerData=" + hasCallerData +
", marker=" + marker +
", mdc=" + mdc +
'}';
}
}