Package com.google.appengine.tools.appstats

Source Code of com.google.appengine.tools.appstats.AppstatsFilter

// Copyright 2009 Google Inc. All Rights Reserved.

package com.google.appengine.tools.appstats;

import com.google.apphosting.api.ApiProxy;
import com.google.apphosting.api.ApiProxy.Delegate;
import com.google.apphosting.api.ApiProxy.Environment;
import com.google.apphosting.api.DeadlineExceededException;
import com.google.common.base.Preconditions;

import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.logging.Logger;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* A servlet filter that will time RPCs going to the server and collect statistics.
* Add this filter to any servlet that you would like to monitor.
*
* The simplest way to configure an application for appstats collection is this:
*
* <p><pre>{@code
<filter>
*   <filter-name>appstats</filter-name>
*   <filter-class>com.google.appengine.tools.appstats.AppstatsFilter</filter-class>
</filter>
<filter-mapping>
*   <filter-name>appstats</filter-name>
*   <url-pattern>/*</url-pattern>
</filter-mapping>
*  }</pre>
*
*/
public class AppstatsFilter implements Filter {

  /**
   * Visible for testing
   */
  static final String DEADLINE_MESSAGE = "Deadline exceeded; cannot log app stats";

  /**
   * Visible for testing.
   */
  static Logger log = Logger.getLogger(AppstatsFilter.class.getName());

  /**
   * Name of the HTTP header that will be included in the response.
   */
  static final String TRACE_HEADER_NAME = "X-TraceUrl";

  /**
   * The default values for the basePath init parameter.
   */
  private static final String DEFAULT_BASE_PATH = "/appstats/";

  /**
   * Threadsafe utility to manage appstats writes.
   *
   */
  private Recording recording;

  /**
   * The delegate that was wrapped when the WRITER was created.
   * Visible for testing.
   */
  static Delegate<?> delegate;

  /**
   * The base path where the AppStats dashboard can be found. This is provided
   * as an init parameter.
   */
  private String basePath;

  /**
   * A log messsage that may be used to store a link back to app stats.
   * The ID is referred to as {ID}.
   */
  private String logMessage;

  private Recorder recorder;

  public AppstatsFilter() {
  }

  /**
   * Visible for testing
   */
  AppstatsFilter(String basePath, String logMessage, Recording recording) {
    this.basePath = basePath;
    this.logMessage = logMessage;
    this.recording = recording;
  }

  @Override
  public void destroy() {
  }

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain filters)
      throws IOException, ServletException {
    Preconditions.checkNotNull(recording, "recording shouldn't be null");
    Environment environment = getCurrentEnvironment();
    Long id = recording.begin(delegate, environment, (HttpServletRequest) request);
    final HttpServletResponse innerResponse = (HttpServletResponse) response;
    final int[] responseCode = {0};

    innerResponse.addHeader(TRACE_HEADER_NAME,
        basePath + "details?time=" + id + "&type=json");

    InvocationHandler invocationHandler =  new InvocationHandler() {
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("sendError") || method.getName().equals("setStatus")) {
          responseCode[0] = ((int) args[0]);
        } else if (method.getName().equals("sendRedirect")) {
          responseCode[0] = HttpServletResponse.SC_TEMPORARY_REDIRECT;
        }
        return call(method, innerResponse, args);
      }
    };
    final HttpServletResponse outerResponse = (HttpServletResponse) Proxy.newProxyInstance(
        AppstatsFilter.class.getClassLoader(),
        new Class<?>[] {HttpServletResponse.class},
        invocationHandler);
    try {
      filters.doFilter(request, outerResponse);
    } catch (DeadlineExceededException e) {
      id = null;
      log.warning(DEADLINE_MESSAGE);
      throw e;
    } finally {
      if (id != null) {
        if (recorder != null) {
          recorder.processAsyncRpcs(environment);
        }
        recording.finishCustomRecordings();
        boolean didCommit = recording.commit(delegate, environment, responseCode[0]);
        if (logMessage != null && didCommit) {
          log.info(logMessage.replace("{ID}", "" + id));
        }
      }
    }
  }

  private static String getAppStatsPathFromConfig(FilterConfig config) {
    String path = config.getInitParameter("basePath");
    if (path == null) {
      return DEFAULT_BASE_PATH;
    } else {
      return path.endsWith("/") ? path : path + "/";
    }
  }

  @SuppressWarnings("unchecked")
  @Override
  public synchronized void init(FilterConfig config) {
    if (recording == null) {
      AppstatsSettings settings = initializeSettings(config);
      recording = new Recording(settings);
      getCurrentEnvironment().getAttributes().put(Recording.RECORDING_KEY, recording);
      Recorder.RecordWriter newWriter = recording.getWriter();
      delegate = ApiProxy.getDelegate();

      recorder = new Recorder(delegate, newWriter, settings);
      ApiProxy.setDelegate(wrapPartially(delegate, recorder));
    }
    basePath = getAppStatsPathFromConfig(config);
    logMessage = config.getInitParameter("logMessage");
  }

  /**
   * Create a proxy that implements all the interfaces that the original
   * implements. Whenever a method is called that the wrapper supports,
   * the wrapper will be called. Otherwise, the method will be invoked on
   * the original object.
   */
  static <S, T extends S> S wrapPartially(final S original, final T wrapper) {

    if (!original.getClass().getName().contains("Local")) {
      return wrapper;
    }

    Class<?>[] interfaces = original.getClass().getInterfaces();
    InvocationHandler handler = new InvocationHandler(){
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Method wrapperMethod = null;
        try {
          wrapperMethod = wrapper.getClass().getMethod(
              method.getName(), method.getParameterTypes());
        } catch (NoSuchMethodException e) {
          return call(method, original, args);
        }
        return call(wrapperMethod, wrapper, args);
      }};
    @SuppressWarnings("unchecked")
    S ret = (S) Proxy.newProxyInstance(original.getClass().getClassLoader(), interfaces, handler);
    return ret;
  }

  /**
   * Invoke a method and unwrap exceptions the invoked method may throw.
   */
  private static Object call(Method m, Object o, Object[] args) throws Throwable {
    try {
      return m.invoke(o, args);
    } catch (InvocationTargetException e) {
      throw e.getTargetException();
    }
  }

  /**
   * Visible for testing
   */
  Environment getCurrentEnvironment() {
    return ApiProxy.getCurrentEnvironment();
  }

  static AppstatsSettings initializeSettings(FilterConfig config) {
    Preconditions.checkNotNull(config, "FilterConfig can not be null.");
    AppstatsSettings settings = AppstatsSettings.withDefault();
    settings.setMaxLinesOfStackTrace(
        getPositiveInt(config, "maxLinesOfStackTrace", Integer.MAX_VALUE));
    if (config.getInitParameter("payloadRenderer") != null) {
      settings.setPayloadRenderer(config.getInitParameter("payloadRenderer"));
    }
    if (config.getInitParameter("onPendingAsyncCall") != null) {
      settings.setUnprocessedFutureStrategy(config.getInitParameter("onPendingAsyncCall"));
    }
    if (config.getInitParameter("calculateRpcCosts") != null) {
      settings.setCalculateRpcCosts(config.getInitParameter("calculateRpcCosts"));
    }
    if (config.getInitParameter("datastoreDetails") != null) {
      settings.setDatastoreDetails(config.getInitParameter("datastoreDetails"));
    }

    return settings;
  }

  static int getPositiveInt(FilterConfig config, String key, int defaultValue) {
    int result = defaultValue;
    String stringValue = config.getInitParameter(key);
    if (stringValue != null) {
      result = Integer.parseInt(stringValue);
      if (result <= 0) {
        throw new IllegalArgumentException(key + " must be a positive value");
      }
    }
    return result;
  }
}
TOP

Related Classes of com.google.appengine.tools.appstats.AppstatsFilter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.