All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.cisco.oss.foundation.logging.structured.AbstractFoundationLoggingMarker Maven / Gradle / Ivy

There is a newer version: 1.2.1-3
Show newest version
/*
 * 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 markerClass) {
		try {

//			generateAndUpdateFormatterInMap(markerClass);


			Class formatterClass = null;
			try {
				formatterClass = (Class)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 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 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 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 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 clazz = (Class) 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