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

com.googlecode.objectify.insight.Codepointer Maven / Gradle / Ivy

package com.googlecode.objectify.insight;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.concurrent.ConcurrentHashMap;

import javax.inject.Singleton;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import com.google.common.io.BaseEncoding;
import com.googlecode.objectify.insight.util.StackTraceUtils;

import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.extern.java.Log;

/**
 * Identifies codepoints. Also logs codepoints which have never been seen before so that developers
 * can simply grep logs for the codepoint hash. It'll be in the logs somewhere.
 */
@Singleton
@Log
@Accessors(chain=true)
public class Codepointer {
	
	/** If set true, we will not record code points - they will all be empty strings */
	@Getter @Setter
	private boolean disabled;
	
	@Getter @Setter
	private StackProducer stackProducer = new LegacyStackProducer();
	
	/** Track which ones we've logged already. It's a Set, just map to the key value */
	private ConcurrentHashMap logged = new ConcurrentHashMap<>();
	
	/**
	 * Get the hash of the code point. Also logs the definition of the code point, once per codepoint (per instance).
	 */
	public String getCodepoint() {
		if (disabled)
			return "disabled";
		
		String stack = stack();
		String digest = digest(stack);
		
		if (logged.putIfAbsent(digest, digest) == null) {
			log.info("Codepoint " + digest + " is " + stack);
		}
		
		return digest;
	}
	
	@VisibleForTesting
	String stack() {
		return stackProducer.getStack();
	}
	
	/** Give a hex encoded digest of the string */
	@VisibleForTesting
	String digest(String str) {
		// Checked exceptions are retarded
		
		final MessageDigest md;
		try {
			md = MessageDigest.getInstance("MD5");
		} catch (NoSuchAlgorithmException e) {
			throw new RuntimeException("Impossible", e);
		}
		
		try {
			return BaseEncoding.base16().encode(md.digest(str.getBytes("UTF-8")));
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException("Impossible", e);
		}
	}
	
	public abstract static class StackProducer {
		protected abstract String getStack();
	}
	
	public static class LegacyStackProducer extends StackProducer {
		@Override
		protected String getStack() {
			// It's tempting to getStackTrace() so we can skip all the Insight noise, but that would
			// clone the stacktrace which seems like extra gc work.
			StringWriter stackWriter = new StringWriter(1024);
			new Exception().printStackTrace(new PrintWriter(stackWriter));
			String stack = stackWriter.toString();
			stack = StackTraceUtils.removeMutableEnhancements(stack);
			return stack;
		}
	}
	
	public static abstract class FilteringStackProducer extends StackProducer {
		@Override
		protected String getStack() {
			Exception e = new Exception();
			Iterable stackTrace = filterStack(Arrays.asList(e.getStackTrace()));
			
			StringBuilder sb = new StringBuilder(1024);
			sb.append(e).append("\r\n");
			for (StackTraceElement ste : stackTrace) {
				sb.append("\tat ").append(ste.toString()).append("\r\n");
			}
			return sb.toString();
		}
		
		/**
		 * Implement if you want to modify the stacktrace used for codepoint generation.
* Useful for removing meaningless stack trace elements (for example: servlets, filters, etc). */ protected abstract Iterable filterStack(Iterable stack); /** * Removes stack trace elements from classes whose name - not simple name (!) - starts with the provided prefix. */ protected static Predicate removeStackTraceElementsFromPackage(final String classNamePrefix) { return new Predicate() { @Override public boolean apply(StackTraceElement ste) { return !ste.getClassName().startsWith(classNamePrefix); } }; } protected static Predicate removeStackTraceElementsWithMutableEnhancements() { return new Predicate() { @Override public boolean apply(StackTraceElement ste) { return !StackTraceUtils.containsMutableEnhancements(ste.getClassName()); } }; } } public static class AdvancedStackProducer extends FilteringStackProducer { @Override protected Iterable filterStack(Iterable stack) { return FluentIterable.from(stack) // mutable enhancements .filter(removeStackTraceElementsWithMutableEnhancements()) // basic appengine servlet classes .filter(removeStackTraceElementsFromPackage("com.google.apphosting.")) .filter(removeStackTraceElementsFromPackage("com.google.tracing.")) .filter(removeStackTraceElementsFromPackage("java.lang.Thread")) .filter(removeStackTraceElementsFromPackage("javax.servlet.http.")) .filter(removeStackTraceElementsFromPackage("org.mortbay.jetty.")) .filter(removeStackTraceElementsFromPackage("org.eclipse.jetty.")) // proxy and reflection classes .filter(removeStackTraceElementsFromPackage("com.sun.proxy.")) .filter(removeStackTraceElementsFromPackage("java.lang.reflect.")) .filter(removeStackTraceElementsFromPackage("java.security.")) .filter(removeStackTraceElementsFromPackage("sun.reflect.")) // guice .filter(removeStackTraceElementsFromPackage("com.google.inject.")) // cloud endpoints .filter(removeStackTraceElementsFromPackage("com.google.api.server.spi.")) // gwt rpc .filter(removeStackTraceElementsFromPackage("com.google.gwt.user.server.rpc.")) // objectify, objectify-insight .filter(removeStackTraceElementsFromPackage("com.googlecode.objectify.")); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy