blob: 41321199c7b36e28077a78ae316ccb119858286d [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.server;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.Locale;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.DispatcherType;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.http.HttpServletRequest;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandler.Context;
import org.eclipse.jetty.server.handler.ContextHandler.ContextScopeListener;
import org.eclipse.jetty.util.DateCache;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.annotation.Name;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/** A Context Listener that produces additional debug.
* This listener if added to a ContextHandler, will produce additional debug information to
* either/or a specific log stream or the standard debug log.
* The events produced by {@link ServletContextListener}, {@link ServletRequestListener},
* {@link AsyncListener} and {@link ContextScopeListener} are logged.
*/
@ManagedObject("Debug Listener")
public class DebugListener extends AbstractLifeCycle implements ServletContextListener
{
private static final Logger LOG = Log.getLogger(DebugListener.class);
private static final DateCache __date=new DateCache("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH);
private final String _attr = String.format("__R%s@%x",this.getClass().getSimpleName(),System.identityHashCode(this));
private final PrintStream _out;
private boolean _renameThread;
private boolean _showHeaders;
private boolean _dumpContext;
public DebugListener()
{
this(null,false,false,false);
}
public DebugListener(@Name("renameThread") boolean renameThread, @Name("showHeaders") boolean showHeaders, @Name("dumpContext") boolean dumpContext)
{
this(null,renameThread,showHeaders,dumpContext);
}
public DebugListener(@Name("outputStream") OutputStream out, @Name("renameThread") boolean renameThread, @Name("showHeaders") boolean showHeaders, @Name("dumpContext") boolean dumpContext)
{
_out=out==null?null:new PrintStream(out);
_renameThread=renameThread;
_showHeaders=showHeaders;
_dumpContext=dumpContext;
}
@ManagedAttribute("Rename thread within context scope")
public boolean isRenameThread()
{
return _renameThread;
}
public void setRenameThread(boolean renameThread)
{
_renameThread = renameThread;
}
@ManagedAttribute("Show request headers")
public boolean isShowHeaders()
{
return _showHeaders;
}
public void setShowHeaders(boolean showHeaders)
{
_showHeaders = showHeaders;
}
@ManagedAttribute("Dump contexts at start")
public boolean isDumpContext()
{
return _dumpContext;
}
public void setDumpContext(boolean dumpContext)
{
_dumpContext = dumpContext;
}
@Override
public void contextInitialized(ServletContextEvent sce)
{
sce.getServletContext().addListener(_servletRequestListener);
ContextHandler handler = ContextHandler.getContextHandler(sce.getServletContext());
handler.addEventListener(_contextScopeListener);
String cname=findContextName(sce.getServletContext());
log("^ ctx=%s %s",cname,sce.getServletContext());
if (_dumpContext)
{
if (_out==null)
handler.dumpStdErr();
else
{
try
{
handler.dump(_out);
}
catch(Exception e)
{
LOG.warn(e);
}
}
}
}
@Override
public void contextDestroyed(ServletContextEvent sce)
{
String cname=findContextName(sce.getServletContext());
log("v ctx=%s %s",cname,sce.getServletContext());
}
protected String findContextName(ServletContext context)
{
if (context==null)
return null;
String n = (String)context.getAttribute(_attr);
if (n==null)
{
n=String.format("%s@%x",context.getContextPath(),context.hashCode());
context.setAttribute(_attr,n);
}
return n;
}
protected String findRequestName(ServletRequest request)
{
if (request==null)
return null;
HttpServletRequest r = (HttpServletRequest)request;
String n = (String)request.getAttribute(_attr);
if (n==null)
{
n=String.format("%s@%x",r.getRequestURI(),request.hashCode());
request.setAttribute(_attr,n);
}
return n;
}
protected void log(String format, Object... arg)
{
if (!isRunning())
return;
String s=String.format(format,arg);
long now = System.currentTimeMillis();
long ms = now%1000;
if (_out!=null)
_out.printf("%s.%03d:%s%n",__date.formatNow(now),ms,s);
if (LOG.isDebugEnabled())
LOG.info(s);
}
final AsyncListener _asyncListener = new AsyncListener()
{
@Override
public void onTimeout(AsyncEvent event) throws IOException
{
String cname=findContextName(((AsyncContextEvent)event).getServletContext());
String rname=findRequestName(event.getAsyncContext().getRequest());
log("! ctx=%s r=%s onTimeout %s",cname,rname,((AsyncContextEvent)event).getHttpChannelState());
}
@Override
public void onStartAsync(AsyncEvent event) throws IOException
{
String cname=findContextName(((AsyncContextEvent)event).getServletContext());
String rname=findRequestName(event.getAsyncContext().getRequest());
log("! ctx=%s r=%s onStartAsync %s",cname,rname,((AsyncContextEvent)event).getHttpChannelState());
}
@Override
public void onError(AsyncEvent event) throws IOException
{
String cname=findContextName(((AsyncContextEvent)event).getServletContext());
String rname=findRequestName(event.getAsyncContext().getRequest());
log("!! ctx=%s r=%s onError %s %s",cname,rname,event.getThrowable(),((AsyncContextEvent)event).getHttpChannelState());
}
@Override
public void onComplete(AsyncEvent event) throws IOException
{
AsyncContextEvent ace=(AsyncContextEvent)event;
String cname=findContextName(ace.getServletContext());
String rname=findRequestName(ace.getAsyncContext().getRequest());
Request br=Request.getBaseRequest(ace.getAsyncContext().getRequest());
Response response = br.getResponse();
String headers=_showHeaders?("\n"+response.getHttpFields().toString()):"";
log("! ctx=%s r=%s onComplete %s %d%s",cname,rname,ace.getHttpChannelState(),response.getStatus(),headers);
}
};
final ServletRequestListener _servletRequestListener = new ServletRequestListener()
{
@Override
public void requestInitialized(ServletRequestEvent sre)
{
String cname=findContextName(sre.getServletContext());
HttpServletRequest r = (HttpServletRequest)sre.getServletRequest();
String rname=findRequestName(r);
DispatcherType d = r.getDispatcherType();
if (d==DispatcherType.REQUEST)
{
Request br=Request.getBaseRequest(r);
String headers=_showHeaders?("\n"+br.getMetaData().getFields().toString()):"";
StringBuffer url=r.getRequestURL();
if (r.getQueryString()!=null)
url.append('?').append(r.getQueryString());
log(">> %s ctx=%s r=%s %s %s %s %s %s%s",d,
cname,
rname,
d,
r.getMethod(),
url.toString(),
r.getProtocol(),
br.getHttpChannel(),
headers);
}
else
log(">> %s ctx=%s r=%s",d,cname,rname);
}
@Override
public void requestDestroyed(ServletRequestEvent sre)
{
String cname=findContextName(sre.getServletContext());
HttpServletRequest r = (HttpServletRequest)sre.getServletRequest();
String rname=findRequestName(r);
DispatcherType d = r.getDispatcherType();
if (sre.getServletRequest().isAsyncStarted())
{
sre.getServletRequest().getAsyncContext().addListener(_asyncListener);
log("<< %s ctx=%s r=%s async=true",d,cname,rname);
}
else
{
Request br=Request.getBaseRequest(r);
String headers=_showHeaders?("\n"+br.getResponse().getHttpFields().toString()):"";
log("<< %s ctx=%s r=%s async=false %d%s",d,cname,rname,Request.getBaseRequest(r).getResponse().getStatus(),headers);
}
}
};
final ContextHandler.ContextScopeListener _contextScopeListener = new ContextHandler.ContextScopeListener()
{
@Override
public void enterScope(Context context, Request request, Object reason)
{
String cname=findContextName(context);
if (request==null)
log("> ctx=%s %s",cname,reason);
else
{
String rname=findRequestName(request);
if (_renameThread)
{
Thread thread=Thread.currentThread();
thread.setName(String.format("%s#%s",thread.getName(),rname));
}
log("> ctx=%s r=%s %s",cname,rname,reason);
}
}
@Override
public void exitScope(Context context, Request request)
{
String cname=findContextName(context);
if (request==null)
log("< ctx=%s",cname);
else
{
String rname=findRequestName(request);
log("< ctx=%s r=%s",cname,rname);
if (_renameThread)
{
Thread thread=Thread.currentThread();
if (thread.getName().endsWith(rname))
thread.setName(thread.getName().substring(0,thread.getName().length()-rname.length()-1));
}
}
}
};
}