com.cisco.oss.foundation.logging.structured.AbstractFoundationLoggingMarker Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of logging-api Show documentation
Show all versions of logging-api Show documentation
This project is the logging api library in the cisco vss foundation runtime
/*
* Copyright 2015 Cisco Systems, 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 com.cisco.oss.foundation.logging.structured;
import com.cisco.oss.foundation.logging.FoundationLoggerConstants;
import com.google.common.collect.ConcurrentHashMultiset;
import com.google.common.collect.Multiset;
import org.apache.commons.lang3.text.WordUtils;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.input.SAXBuilder;
import org.reflections.Reflections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import javax.tools.*;
import javax.tools.JavaFileObject.Kind;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.net.URI;
import java.security.SecureClassLoader;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public abstract class AbstractFoundationLoggingMarker implements FoundationLoggingMarker {
private boolean isInit = false;
private static final long serialVersionUID = 6354894754547315308L;
protected Map userFields = new ConcurrentHashMap();
private static final Map, Multiset> markerClassFields = new ConcurrentHashMap, Multiset>();
private static HashMap, Class> markersMap = new HashMap, Class>();
public static HashMap markersXmlMap = new HashMap();
private FoundationLoggingMarkerFormatter formatter;
private static Logger LOGGER = LoggerFactory.getLogger(AbstractFoundationLoggingMarker.class);
public static void init(){
if(LOGGER == null){
LOGGER = LoggerFactory.getLogger(AbstractFoundationLoggingMarker.class);
}
udpateMarkerStructuredLogOverrideMap();
new Thread(new Runnable() {
@Override
public void run() {
try {
scanClassPathForFormattingAnnotations();
} catch (Exception e) {
LOGGER.error("Problem parsing marker annotations. Error is: " + e, e);
}
}
}).start();
}
private static void udpateMarkerStructuredLogOverrideMap() {
InputStream messageFormatIS = AbstractFoundationLoggingMarker.class.getResourceAsStream("/messageFormat.xml");
if (messageFormatIS == null) {
LOGGER.debug("file messageformat.xml not found in classpath");
} else {
try {
SAXBuilder builder = new SAXBuilder();
Document document = builder.build(messageFormatIS);
messageFormatIS.close();
Element rootElement = document.getRootElement();
List markers = rootElement.getChildren("marker");
for (Element marker : markers) {
AbstractFoundationLoggingMarker.markersXmlMap.put(marker.getAttributeValue("id"), marker);
}
} catch (Exception e) {
LOGGER.error("cannot load the structured log override file. error is: " + e, e);
throw new IllegalArgumentException("Problem parsing messageformat.xml", e);
}
}
}
public AbstractFoundationLoggingMarker() {
super();
// create new instance of a formatter specific to this marker
// implementation.
// if it is the first time we create a marker of this class - we
// generate a new formatter implementation
FoundationLoggingMarkerFormatter newFormatter = getFormatter(getClass());
newFormatter.setMarker(this);
setFormatter(newFormatter);
}
private static FoundationLoggingMarkerFormatter getFormatter(Class extends FoundationLoggingMarker> markerClass) {
try {
// generateAndUpdateFormatterInMap(markerClass);
Class extends FoundationLoggingMarkerFormatter> formatterClass = null;
try {
formatterClass = (Class extends FoundationLoggingMarkerFormatter>)Class.forName(markerClass.getName() + "Formatter");
} catch (ClassNotFoundException e) {
LOGGER.error("annotation processign ahs failed. " + e.toString());
}
FoundationLoggingMarkerFormatter newFormatter = null;
if (formatterClass == null) {
newFormatter = new DefaultMarkerFormatter();
} else {
newFormatter = formatterClass.newInstance();
}
return newFormatter;
} catch (Exception e) {
LOGGER.error("Can't find a proxied class for: " + markerClass, e);
throw new IllegalArgumentException(e);
}
}
private static void generateAndUpdateFormatterInMap(Class extends FoundationLoggingMarker> markerClass) {
if (markersMap.get(markerClass) == null) {
Element markerElement = markersXmlMap.get(markerClass.getName());
StringBuilder builder = new StringBuilder();
buildClassPrefix(markerClass, builder);
boolean shouldGenereate = buildFormat(markerElement, markerClass, builder);
if (shouldGenereate) {
buildClassSuffix(builder);
String newClassSource = builder.toString();
synchronized (markerClass) {
LOGGER.trace("The generated class is: {}", newClassSource);
Class formatterClass = generateMarkerClass(newClassSource, markerClass.getName() + "Formatter");
markersMap.put(markerClass, formatterClass);
}
} else {
LOGGER.debug("Not generating any specific markers for {} as it doesn't contain any annotations", markerClass);
}
}
}
@Override
public String getName() {
if (!isInit) {
populateUserFieldMap();
}
return this.getClass().getSimpleName();
}
@Override
public void add(Marker reference) {
}
@Override
public boolean remove(Marker reference) {
return false;
}
@Override
public boolean hasChildren() {
return false;
}
@Override
public boolean hasReferences() {
return false;
}
@Override
public Iterator iterator() {
return null;
}
@Override
public boolean contains(Marker other) {
return false;
}
@Override
public boolean contains(String name) {
return false;
}
@Override
public String valueOf(String userFieldName) {
String value = null;
if (FoundationLoggerConstants.TRANSACTION_NAME.toString().equals(userFieldName)) {
value = getName();
} else if (FoundationLoggerConstants.ALL_VALUES.toString().equals(userFieldName)) {
value = buildAllValues();
} else {
value = userFields.get(userFieldName) != null ? userFields.get(userFieldName).toString() : null;
}
return value;
}
private String buildAllValues() {
boolean first = true;
StringBuilder builder = new StringBuilder("{");
Set> entrySet = userFields.entrySet();
for (Entry entry : entrySet) {
String key = entry.getKey();
Object value = entry.getValue();
if (!FoundationLoggingMarker.NO_OPERATION.equals(value)) {
if (first) {
first = false;
} else {
builder.append(",");
}
builder.append("\"");
builder.append(key);
builder.append("\"");
builder.append(":");
if (value instanceof String) {
builder.append("\"");
value = ((String) value).replaceAll("\"", "'");
builder.append(value);
builder.append("\"");
} else {
builder.append(value);
}
}
}
builder.append("}");
return builder.toString();
}
protected void populateUserFieldMap() {
Class extends AbstractFoundationLoggingMarker> clazz = this.getClass();
Multiset fieldList = markerClassFields.get(clazz);
if (fieldList == null) {
fieldList = ConcurrentHashMultiset.create();
Class> cls = clazz;
while (AbstractFoundationLoggingMarker.class.isAssignableFrom(cls)){
Field[] declaredFields = cls.getDeclaredFields();
for (Field field : declaredFields) {
if (field.isAnnotationPresent(UserField.class)) {
field.setAccessible(true);
fieldList.add(field);
}
}
markerClassFields.put(clazz, fieldList);
cls = cls.getSuperclass();
}
}
for (Field field : fieldList) {
try {
Object value = field.get(this);
UserField userField = field.getAnnotation(UserField.class);
if (value == null && userField.suppressNull()) {
value = FoundationLoggingMarker.NO_OPERATION;
}
userFields.put(field.getName(), value == null ? "null" : value);
} catch (IllegalAccessException e) {
throw new IllegalArgumentException(e);
}
}
}
@Override
public FoundationLoggingMarkerFormatter getFormatter() {
return formatter;
}
public void setFormatter(FoundationLoggingMarkerFormatter formatter) {
this.formatter = formatter;
}
private static Class generateMarkerClass(String sourceCode, String newClassName) {
try {
// We get an instance of JavaCompiler. Then
// we create a file manager
// (our custom implementation of it)
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
JavaFileManager fileManager = new ClassFileManager(compiler.getStandardFileManager(null, null, null));
// Dynamic compiling requires specifying
// a list of "files" to compile. In our case
// this is a list containing one "file" which is in our case
// our own implementation (see details below)
List jfiles = new ArrayList();
jfiles.add(new DynamicJavaSourceCodeObject(newClassName, sourceCode));
List optionList = new ArrayList();
// set compiler's classpath to be same as the runtime's
optionList.addAll(Arrays.asList("-classpath", System.getProperty("java.class.path")));
// We specify a task to the compiler. Compiler should use our file
// manager and our list of "files".
// Then we run the compilation with call()
compiler.getTask(null, fileManager, null, optionList, null, jfiles).call();
// Creating an instance of our compiled class and
// running its toString() method
Class clazz = (Class) fileManager.getClassLoader(null).loadClass(newClassName);
return clazz;
} catch (Exception e) {
throw new UnsupportedOperationException("can't create class of: " + newClassName, e);
}
}
private static void buildClassPrefix(Class markerClass, StringBuilder builder) {
builder.append("\npackage ").append(markerClass.getPackage().getName()).append(";\n\n");
builder.append("import ").append(FoundationLoggingMarkerFormatter.class.getName()).append(";\n");
builder.append("import ").append(FoundationLoggingMarker.class.getName()).append(";\n");
// builder.append("import ").append(FoundationLoggingEvent.class.getName()).append(";\n");
builder.append("public class ").append(markerClass.getSimpleName()).append("Formatter").append(" implements FoundationLoggingMarkerFormatter ").append(" {\n\n");
builder.append("private FoundationLoggingMarker loggingMarker;\n\n");
builder.append(" \n@Override\n" + " public void setMarker(FoundationLoggingMarker marker) {\r\n" + " this.loggingMarker = marker;\r\n" + " }");
builder.append("\n@Override\npublic String getFormat(String appenderName) {\n").append(markerClass.getSimpleName()).append(" marker = (").append(markerClass.getSimpleName()).append(")loggingMarker;\n");
}
private static void buildClassSuffix(StringBuilder builder) {
builder.append("}\n}");
}
private static boolean buildFormat(Element markerElement, Class extends FoundationLoggingMarker> clazz, StringBuilder builder) {
if (markerElement == null) {
return buildFromAnnotations(clazz, builder);
} else {
return buildFromXml(markerElement, builder);
}
}
private static boolean buildFromXml(Element markerElement, StringBuilder builder) {
Element defaultAppender = markerElement.getChild("defaultAppender");
List appenders = markerElement.getChildren("appender");
String markerId = markerElement.getAttributeValue("id");
if (appenders != null) {
for (Element appender : appenders) {
String appenderId = appender.getAttributeValue("id");
if (appenderId == null) {
LOGGER.error("the appender element must have an id poiting to a valid appender name");
} else {
buildFromAppenderElement(markerId, appenderId, appender, builder, false, appenderId);
}
}
}
if (defaultAppender == null) {
LOGGER.error("The marker element: '{}' must contain a 'defaultAppender' element", markerId);
builder.append("return null;");
} else {
buildFromAppenderElement(markerId, "defaultAppender", defaultAppender, builder, true, "DEFAULT");
}
return true;
}
private static void buildFromAppenderElement(String markerId, String appenderId, Element appenderElement, StringBuilder builder, boolean isDefault, String appenderName) {
if (!isDefault) {
builder.append("if (\"").append(appenderName).append("\".equals(").append("appenderName)){\n");
}
Element criteriaElement = appenderElement.getChild("criteria");
if (criteriaElement != null) {
List criterionList = criteriaElement.getChildren("criterion");
if (criterionList != null) {
for (Element criterionElement : criterionList) {
String result = criterionElement.getAttributeValue("format");
List fieldList = criterionElement.getChildren("field");
if (fieldList != null) {
for (int i = 0; i < fieldList.size(); i++) {
Element fieldElement = fieldList.get(i);
String key = fieldElement.getAttributeValue("name");
String value = fieldElement.getAttributeValue("equals");
String getterField = "marker.get" + WordUtils.capitalize(key) + "()";
if (i == 0) {
builder.append("if (").append(getterField).append(" != null && \"").append(value).append("\".equals(").append(getterField).append(".toString())");
} else {
builder.append(" && ").append(getterField).append(" != null && \"").append(value).append("\".equals(").append(getterField).append(".toString())");
}
}
builder.append(")\n\treturn \"").append(result).append("\";\n");
}
}
}
} else {
LOGGER.info("The marker element '{}' does not contain a 'criteria' element for appender: '{}'", markerId, appenderId);
}
String defaultFormat = appenderElement.getAttributeValue("defaultFormat");
if (defaultFormat == null) {
LOGGER.error("The marker element: '{}' must contain a 'defaultFormat' element", markerId);
}
builder.append("return \"" + defaultFormat + "\";");
if (!isDefault) {
builder.append("\n}\n");
}
}
private static boolean buildFromAnnotations(Class extends FoundationLoggingMarker> clazz, StringBuilder builder) {
boolean shouldGenerate = false;
final DefaultFormat defaultFormat = clazz.getAnnotation(DefaultFormat.class);
ConditionalFormats conditionalFormats = clazz.getAnnotation(ConditionalFormats.class);
if (conditionalFormats != null) {
shouldGenerate = true;
if (defaultFormat == null) {
throw new IllegalArgumentException("when using conditionals - you must also speicfy a DefaultFormat annotation");
}
ConditionalFormat[] formats = conditionalFormats.value();
if (formats != null) {
for (ConditionalFormat conditionalFormat : formats) {
final ConditionalFormat format = conditionalFormat;
buldConditionalFormat(format, builder);
}
}
}
if (defaultFormat != null) {
shouldGenerate = true;
buildDefaultFormat(defaultFormat, builder);
}
return shouldGenerate;
}
private static void buldConditionalFormat(ConditionalFormat format, final StringBuilder builder) {
String result = format.format();
FieldCriterion[] criteria = format.criteria();
if (criteria != null) {
for (int i = 0; i < criteria.length; i++) {
FieldCriterion criterion = criteria[i];
String key = criterion.name();
String value = criterion.value();
String getterField = "marker.get" + WordUtils.capitalize(key) + "()";
if (i == 0) {
builder.append("if (").append(getterField).append(" != null && \"").append(value).append("\".equals(").append(getterField).append(".toString())");
} else {
builder.append(" && ").append(getterField).append(" != null && \"").append(value).append("\".equals(").append(getterField).append(".toString())");
}
}
builder.append(")\n\treturn \"").append(result).append("\";\n");
}
}
private static void buildDefaultFormat(DefaultFormat defaultFormat, final StringBuilder builder) {
builder.append("return \"" + defaultFormat.value() + "\";");
}
/**
* Scan all the class path and look for all classes that have the Format
* Annotations.
*/
public static void scanClassPathForFormattingAnnotations() {
ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
// scan classpath and filter out classes that don't begin with "com.nds"
Reflections reflections = new Reflections("com.nds","com.cisco");
Set> annotated = reflections.getTypesAnnotatedWith(DefaultFormat.class);
// Reflections ciscoReflections = new Reflections("com.cisco");
//
// annotated.addAll(ciscoReflections.getTypesAnnotatedWith(DefaultFormat.class));
for (Class> markerClass : annotated) {
// if the marker class is indeed implementing FoundationLoggingMarker
// interface
if (FoundationLoggingMarker.class.isAssignableFrom(markerClass)) {
final Class extends FoundationLoggingMarker> clazz = (Class extends FoundationLoggingMarker>) markerClass;
executorService.execute(new Runnable() {
@Override
public void run() {
if (markersMap.get(clazz) == null) {
try {
// generate formatter class for this marker
// class
generateAndUpdateFormatterInMap(clazz);
} catch (Exception e) {
LOGGER.trace("problem generating formatter class from static scan method. error is: " + e.toString());
}
}
}
});
} else {// if marker class does not implement FoundationLoggingMarker
// interface, log ERROR
// verify the LOGGER was initialized. It might not be as this
// Method is called in a static block
if (LOGGER == null) {
LOGGER = LoggerFactory.getLogger(AbstractFoundationLoggingMarker.class);
}
LOGGER.error("Formatter annotations should only appear on foundationLoggingMarker implementations");
}
}
executorService.shutdown();
// try {
// executorService.awaitTermination(15, TimeUnit.SECONDS);
// } catch (InterruptedException e) {
// LOGGER.error("creation of formatters has been interrupted");
// }
}
/**
* Creates a dynamic source code file object
*
* This is an example of how we can prepare a dynamic java source code for
* compilation. This class reads the java code from a string and prepares a
* JavaFileObject
*
*/
private static class DynamicJavaSourceCodeObject extends SimpleJavaFileObject {
private String sourceCode;
/**
* Converts the name to an URI, as that is the format expected by
* JavaFileObject
*
*
* @param name
* qualified name given to the class file
* @param code
* the source code string
*/
protected DynamicJavaSourceCodeObject(String name, String code) {
super(URI.create("string:///" + name.replaceAll("\\.", "/") + Kind.SOURCE.extension), Kind.SOURCE);
this.sourceCode = code;
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
return sourceCode;
}
}
public static class JavaClassObject extends SimpleJavaFileObject {
/**
* Byte code created by the compiler will be stored in this
* ByteArrayOutputStream so that we can later get the byte array out of
* it and put it in the memory as an instance of our class.
*/
protected final ByteArrayOutputStream bos = new ByteArrayOutputStream();
/**
* Registers the compiled class object under URI containing the class
* full name
*
* @param name
* Full name of the compiled class
* @param kind
* Kind of the data. It will be CLASS in our case
*/
public JavaClassObject(String name, Kind kind) {
super(URI.create("string:///" + name.replace('.', '/') + kind.extension), kind);
}
/**
* Will be used by our file manager to get the byte code that can be put
* into memory to instantiate our class
*
* @return compiled byte code
*/
public byte[] getBytes() {
return bos.toByteArray();
}
/**
* Will provide the compiler with an output stream that leads to our
* byte array. This way the compiler will write everything into the byte
* array that we will instantiate later
*/
@Override
public OutputStream openOutputStream() throws IOException {
return bos;
}
}
public static class ClassFileManager extends ForwardingJavaFileManager {
/**
* Instance of JavaClassObject that will store the compiled bytecode of
* our class
*/
private JavaClassObject jclassObject;
/**
* Will initialize the manager with the specified standard java file
* manager
*
* @param standardManager
*/
public ClassFileManager(StandardJavaFileManager standardManager) {
super(standardManager);
}
/**
* Will be used by us to get the class loader for our compiled class. It
* creates an anonymous class extending the SecureClassLoader which uses
* the byte code created by the compiler and stored in the
* JavaClassObject, and returns the Class for it
*/
@Override
public ClassLoader getClassLoader(Location location) {
return new SecureClassLoader() {
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
byte[] b = jclassObject.getBytes();
return super.defineClass(name, jclassObject.getBytes(), 0, b.length);
}
};
}
/**
* Gives the compiler an instance of the JavaClassObject so that the
* compiler can write the byte code into it.
*/
@Override
public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) throws IOException {
jclassObject = new JavaClassObject(className, kind);
return jclassObject;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy