![JAR search and dependency download from the Maven repository](/logo.png)
org.apache.juneau.rest.stats.ThrownStore Maven / Gradle / Ivy
// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file *
// * to you 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 org.apache.juneau.rest.stats;
import static java.util.stream.Collectors.*;
import static org.apache.juneau.internal.CollectionUtils.*;
import static org.apache.juneau.internal.ObjectUtils.*;
import static java.util.Comparator.*;
import java.util.*;
import java.util.concurrent.*;
import org.apache.juneau.*;
import org.apache.juneau.cp.*;
import org.apache.juneau.internal.*;
/**
* An in-memory cache of thrown exceptions.
*
*
* Used for preventing duplication of stack traces in log files and replacing them with small hashes.
*
*
See Also:
*/
public class ThrownStore {
//-----------------------------------------------------------------------------------------------------------------
// Static
//-----------------------------------------------------------------------------------------------------------------
/** Identifies a single global store for the entire JVM. */
public static final ThrownStore GLOBAL = new ThrownStore();
/**
* Static creator.
*
* @param beanStore The bean store to use for creating beans.
* @return A new builder for this object.
*/
public static Builder create(BeanStore beanStore) {
return new Builder(beanStore);
}
/**
* Static creator.
*
* @return A new builder for this object.
*/
public static Builder create() {
return new Builder(BeanStore.INSTANCE);
}
//-----------------------------------------------------------------------------------------------------------------
// Builder
//-----------------------------------------------------------------------------------------------------------------
/**
* Builder class.
*/
@FluentSetters
public static class Builder extends BeanBuilder {
ThrownStore parent;
Class extends ThrownStats> statsImplClass;
Set> ignoreClasses;
/**
* Constructor.
*
* @param beanStore The bean store to use for creating beans.
*/
protected Builder(BeanStore beanStore) {
super(ThrownStore.class, beanStore);
}
@Override /* BeanBuilder */
protected ThrownStore buildDefault() {
return new ThrownStore(this);
}
//-------------------------------------------------------------------------------------------------------------
// Properties
//-------------------------------------------------------------------------------------------------------------
/**
* Specifies a subclass of {@link ThrownStats} to use for individual method statistics.
*
* @param value The new value for this setting.
* @return This object.
*/
public Builder statsImplClass(Class extends ThrownStats> value) {
this.statsImplClass = value;
return this;
}
/**
* Specifies the parent store of this store.
*
*
* Parent stores are used for aggregating statistics across multiple child stores.
*
The {@link ThrownStore#GLOBAL} store can be used for aggregating all thrown exceptions in a single JVM.
*
* @param value The parent store. Can be null .
* @return This object.
*/
public Builder parent(ThrownStore value) {
this.parent = value;
return this;
}
/**
* Specifies the list of classes to ignore when calculating stack traces.
*
*
* Stack trace elements that are the specified class will be ignored.
*
* @param value The list of classes to ignore.
* @return This object.
*/
public Builder ignoreClasses(Class>...value) {
this.ignoreClasses = set(value);
return this;
}
//
@Override /* GENERATED - org.apache.juneau.BeanBuilder */
public Builder impl(Object value) {
super.impl(value);
return this;
}
@Override /* GENERATED - org.apache.juneau.BeanBuilder */
public Builder type(Class> value) {
super.type(value);
return this;
}
//
}
//-----------------------------------------------------------------------------------------------------------------
// Instance
//-----------------------------------------------------------------------------------------------------------------
private final ConcurrentHashMap db = new ConcurrentHashMap<>();
private final Optional parent;
private final BeanStore beanStore;
private final Class extends ThrownStats> statsImplClass;
private final Set ignoreClasses;
/**
* Constructor.
*/
public ThrownStore() {
this(create(BeanStore.INSTANCE));
}
/**
* Constructor.
*
* @param builder The builder for this object.
*/
public ThrownStore(Builder builder) {
this.parent = optional(builder.parent);
this.beanStore = builder.beanStore();
this.statsImplClass = firstNonNull(builder.statsImplClass, parent.isPresent() ? parent.get().statsImplClass : null, null);
Set s = null;
if (builder.ignoreClasses != null)
s = builder.ignoreClasses.stream().map(Class::getName).collect(toSet());
if (s == null && parent.isPresent())
s = parent.get().ignoreClasses;
if (s == null)
s = Collections.emptySet();
this.ignoreClasses = unmodifiable(s);
}
/**
* Adds the specified thrown exception to this database.
*
* @param e The exception to add.
* @return This object.
*/
public ThrownStats add(Throwable e) {
ThrownStats s = find(e);
s.increment();
parent.ifPresent(x->x.add(e));
return s;
}
/**
* Retrieves the stats for the specified thrown exception.
*
* @param e The exception.
* @return A clone of the stats, never null .
*/
public Optional getStats(Throwable e) {
return getStats(hash(e));
}
/**
* Retrieves the stack trace information for the exception with the specified hash as calculated by {@link #hash(Throwable)}.
*
* @param hash The hash of the exception.
* @return A clone of the stack trace info, never null .
*/
public Optional getStats(long hash) {
ThrownStats s = db.get(hash);
return optional(s == null ? null : s.clone());
}
/**
* Returns the list of all stack traces in this database.
*
* @return The list of all stack traces in this database, cloned and sorted by count descending.
*/
public List getStats() {
return db.values().stream().map(ThrownStats::clone).sorted(comparingInt(ThrownStats::getCount).reversed()).collect(toList());
}
/**
* Clears out the stack trace cache.
*/
public void reset() {
db.clear();
}
/**
* Calculates a 32-bit hash for the specified throwable based on the stack trace generated by {@link #createStackTrace(Throwable)}.
*
*
* Subclasses can override this method to provide their own implementation.
*
* @param t The throwable to calculate the stack trace on.
* @return A calculated hash.
*/
protected long hash(Throwable t) {
long h = 1125899906842597L; // prime
for (String s : createStackTrace(t)) {
int len = s.length();
for (int i = 0; i < len; i++)
h = 31*h + s.charAt(i);
}
return h;
}
/**
* Converts the stack trace for the specified throwable into a simple list of strings.
*
*
* The stack trace elements for the throwable are sent through {@link #normalize(StackTraceElement)} to convert
* them to simple strings.
*
*
* @param t The throwable to create the stack trace for.
* @return A modifiable list of strings.
*/
protected List createStackTrace(Throwable t) {
return alist(t.getStackTrace()).stream().filter(this::include).map(this::normalize).collect(toList());
}
/**
* Returns true if the specified stack trace element should be included in {@link #createStackTrace(Throwable)}.
*
* @param e The stack trace element.
* @return true if the specified stack trace element should be included in {@link #createStackTrace(Throwable)}.
*/
protected boolean include(StackTraceElement e) {
return true;
}
/**
* Converts the specified stack trace element into a normalized string.
*
*
* The default implementation simply replaces "\\$.*" with "..." which should take care of stuff like stack
* trace elements of lambda expressions.
*
* @param e The stack trace element to convert.
* @return The converted stack trace element.
*/
protected String normalize(StackTraceElement e) {
if (ignoreClasses.contains(e.getClassName()))
return "";
String s = e.toString();
int i = s.indexOf('$');
if (i == -1)
return s;
int j = s.indexOf('(', i);
if (j == -1)
return s; // Probably can't happen.
String s2 = s.substring(0, i), s3 = s.substring(j);
if (ignoreClasses.contains(s2))
return "";
return s2 + "..." + s3;
}
private ThrownStats find(final Throwable t) {
if (t == null)
return null;
long hash = hash(t);
ThrownStats stc = db.get(hash);
if (stc == null) {
stc = ThrownStats
.create(beanStore)
.type(statsImplClass)
.throwable(t)
.hash(hash)
.stackTrace(createStackTrace(t))
.causedBy(find(t.getCause()))
.build();
db.putIfAbsent(hash, stc);
stc = db.get(hash);
}
return stc;
}
}