I recently discovered the magic of using MDCs when logging. It works perfectly.
I have the following method:
public static final String MDC_CLIENT="client";
public static final String MDC_SESSION="session";
public static final String MDC_DEVICE="device";
// Called for every request.
public static void request(final HttpServletRequest request) {
// The MDC is a thread-local storage accessable from the log formatter.
MDC.put(MDC_CLIENT, String.format("%s:%s", request.getRemoteHost(), request.getRemotePort()));
HttpSession session = request.getSession();
MDC.put(MDC_SESSION, session.getId());
MDC.put(MDC_DEVICE, (String)session.getAttribute("device"));
// Also record the context.
setContext(session.getServletContext());
}
This is called as the first action in every jsp. This allows me to keep track of the details of the session in the log nicely.
However - how do I know when to remove these map entries? What event should I watch for that will allow me to tidy up the map?
I am hosting under Tomcat. If it re-uses threads then I won't leak memory because these are essentially thread-local so each put will overwrite the old put from the last time the thread was used. If it doesn't - or I am hosted under something else that doesn't - I am essentially growing the map potentially forever - or at least until the host is restarted.
I think the essence of my question is - is there an event I can detect that indicates that a particular session or thread is finished with and about to be released.
Rather than putting the call in every JSP I would register a ServletRequestListener, which gets notified both when the request starts and when it finishes:
public class MDCListener implements ServletRequestListener {
public void requestInitialized(ServletRequestEvent e) {
YourUtilityClass.request((HttpServletRequest)e.getServletRequest());
}
public void requestDestroyed(ServletRequestEvent e) {
YourUtilityClass.tearDown((HttpServletRequest)e.getServletRequest());
}
}
Alternatively you could use a filter which lets you wrap around the usual request processing flow:
void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
setupMDC();
chain.doFilter(request, response);
tearDownMDC();
}
Either way you simply register the relevant classes in web.xml and the container should take care of the rest.
Related
I have a Java Spring Boot Application that is collecting metrics with Prometheus. It is a REST API and everything is working.
Spring, by default, provide some metrics. For example, the metric http_server_requests_seconds_count that has the dimensions:
http_server_requests_seconds_count{application="metrics-demo-app", exception="None", instance="host.docker.internal:8080", job="metrics-demo-app", method="GET", outcome="SUCCESS", status="200", uri="/actuator/prometheus"}
By dimension I mean the key/value pairs (application="metrics-demo-app", exception="None", etc...) tags in the example above.
Can I add a new dimension (key/value pair) to those metrics? My idea is when a call to create a user fails, to insert some more info into that metric.
I know that I could create a new custom metric, but I wonder if there are some manner to just add things in an existing one.
Is that possible? How to do that?
Thanks in advance :D
This can be done and how is in the documentation, here:
https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.metrics.supported.system
My code ended like this:
#Configuration
public class MetricsConfig implements WebMvcTagsProvider {
private final WebMvcTagsProvider delegate = new DefaultWebMvcTagsProvider();
#Override
public Iterable<Tag> getTags(HttpServletRequest request, HttpServletResponse response, Object handler, Throwable exception) {
final String channelId = ServiceContextHolder.getContext().getXItauChannelId();
return Tags.of(this.delegate.getTags(request, response, handler, exception)).and("channelId", channelId);
}
#Override
public Iterable<Tag> getLongRequestTags(HttpServletRequest request, Object handler) {
return null;
}
}
I have brief understanding on multi threading in Java web apps. However, I came across an issue while developing our android app which communicates with the server via REST.
Our web application is based on Apache Wicket 8.6 and contains additional HttpServlet endpoints. One endpoint is applied while uploading images from the app via post. This works fine as long as I am uploading only one image at a time. If I perform multiple upload requests in my android app in a quick succession (some milliseconds), only the last upload is performed successfully (it is working fine when I put a second break between the uploads). It seems, as if all requests except the last one are missing the image content read from the input stream of the servlet request. Thus, I am guessing, that I am having a threading problem with my servlet. I would appreciate, if someone could guide me in the right direction to solve this issue.
public class MyServlet extends HttpServlet{
#Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
boolean proceed =true;
Map<String,String[]> parameters = req.getParameterMap();
if(parameters!=null){
//read some parameters which specify the request
}
if(proceed) {
InputStream is = req.getInputStream();
if(is!=null) {
//The result of this output is 0 for the first requests only for the last request it is 1
System.err.println("Stream size: "+is.available());
//do something
}
}
//do something....
}
}
I could, of course, write the images in my android app in one request using multipart but still I'd like to make the servlet thread safe for the occasion that it gets two requests at the same time.
I appreciate your help.
So, after some additional research I discovered, that the input stream is not empty even is.available() returns 0
My problem was something different. I save the uploaded image in a ModeShape repository. The repository session is stored in the wicket application instance. THus, only one ModeSHape repository session exists. When writing the image, it seems as if there was a problem with the ModeShape session. Thus, I put the ModeShape session in a synchronized block and now everything runs fine.
public class MyServlet extends HttpServlet{
#Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
boolean proceed =true;
Map<String,String[]> parameters = req.getParameterMap();
if(parameters!=null){
//read some parameters which specify the request
}
if(proceed) {
String fileName = req.getHeader("fileName");
if(!StringUtils.isEmpty(fileName)) {
InputStream is = req.getInputStream();
if(is!=null) {
//The result of this output is 0 for the first requests only for the last request it is 1
System.err.println("Stream size: "+is.available());
WicketApplication app=null;
try {
app = (WicketApplication)Application.get("wicket");
} catch (Exception e) {
LOG.error("Error while generating WicketApplication object for project "+project.getName(),e);
}
if(app!=null) {
final Session repoSession = app.getRepoSession();
synchronized (repoSession) {
//write image to repository
}
}
}
}
}
//do something....
}
}
I need to figure out a way to save and log a method request and response conditionally, with the condition being the latency of the top-level method crossing the p50 latency. The call visualization is as follows:
topLevel() -> method1() -> method2() -> ... -> makeRequest()
In makeRequest is where the request and response to that request are that I need to log.
But I'll only know if I need to actually log those at some point on the way back up the call stack - if topLevel method is taking too long.
So to me, the only option is to save the request and response in makeRequest no matter what and make that available to the topLevel method. The topLevel method will check if latency is above p50 and conditionally log the request and response.
This all leads to the titular question: How to share memory over long chain of method calls?
I don't want to be passing objects back through multiple method calls, polluting function signatures.
What is the best pattern for this? Maybe using a local cache to save the request and response and then retrieving it in topLevel? Is there an aspect oriented approach to solving this?
As long as you have control of the code for the top level and down through method1 and method2, this really isn't so hard.
You just need to pass the request down through the calling chain, and pass back the response.
topLevel() -> method1(request) -> method2(request) -> ...
-> makeRequest(request) { ... return response; }
To relate this to a real code example, you can look at how the jersey framework works.
Here's an example of a method where the request is injected, and a response is returned.
#POST
#Consumes({MediaType.TEXT_XML})
#Produces({TEXT_XML_UTF_8})
public Response resource(#Context HttpServletRequest servletRequest) throws Exception {
ExternalRequest req = makeRequest(servletRequest.getInputStream());
ExternalResponse resp = externalGateway.doSomething(req);
return Response.ok(wrapResponse(resp)).build();
}
Although Jersey offers some fancy annotations (#Context and so on), there isn't really a distinguishable design pattern here of any significance - you're just passing down the request object and returning a response.
Of course you can also maintain a cache and pass that up the call stack, or really just a wrapper object for a request and a response, but it's very similar to simply passing the request.
This type of functionality is best done using ThreadLocals. Your makeRequest will add the request and response objects into ThreadLocal and then topLevel will remove them and log them if needed. Here is an example:
public class RequestResponseThreadLocal{
public static ThreadLocal<Object[]> requestResponseThreadLocal = new ThreadLocal<>();
}
public class TopLevel{
public void topLevel(){
try{
new Method1().method1();
Object[] requestResponse = RequestResponseThreadLocal.requestResponseThreadLocal.get();
System.out.println( requestResponse[0] + " : " + requestResponse[1] );
}finally{
//make sure to clean up stuff that was added to ThreadLocal otherwise you will end up with memory leak when using Thread pools
RequestResponseThreadLocal.requestResponseThreadLocal.remove();
}
}
}
public class Method1{
public void method1(){
new Method2().method2();
}
}
public class Method2{
public void method2(){
new MakeRequest().makeRequest();
}
}
public class MakeRequest{
public void makeRequest(){
Object request = new Object();
Object response = new Object();
RequestResponseThreadLocal.requestResponseThreadLocal.set( new Object[]{request, response} );
}
}
I am trying to server a particular error page when session timeouts to my users.
For this i configured the error page on my Application's init method.
But this thing is not working.
I set up the session tiemout in 1 minute, after that nothing happen, I went through the logs, but wicket didn't throw any PageExpiredException.
When session timeouts wicket simply logs it as:
Session unbound: C061F4F21C41EDF13C66795DAC9EDD02
Removing data for pages in session with id 'C061F4F21C41EDF13C66795DAC9EDD02'
this is my init method in my customApplication
protected void init() {
super.init();
this.getApplicationSettings().setPageExpiredErrorPage(SessionExpiredPage.class);
...
...
}
my SessionExpiredPage.class
public class SessionExpiredPage extends TecnoAccionPage {
public SessionExpiredPage() {
this.setOutputMarkupId(true);
this.add(new Label("title", "SesiĆ³n Expirada"));
CSSLoader.get().appendCssUntil(this, SessionExpiredPage.class);
}
}
And i have a custom implementation of AbstractRequestCycleListener i override the OnException method But, when my session expire, I never pass in the "onException".
Thank You, best regards.
For some reason there is no PageExpiredException thrown by wicket, while it can reconstruct requested page, even if the session was expired.
So, there is another way to deal with this problem.
You have to override onRequestHandlerResolved method in your AbstractRequestCycleListener, to catch all incoming requests, and check there if incoming session id is outdated.
To check this, you must have list of the expired sessions in your app and catch unbound event to manage them.
This is going to be something like that:
public class YourApp extends WebApplication {
//synchronized list with ids
private List<String> unboundSessions = new CopyOnWriteArrayList<String>();
#Override
protected void init() {
super.init();
this.getApplicationSettings().setPageExpiredErrorPage(SessionExpiredPage.class);
//add request listener
getRequestCycleListeners().add(new AbstractRequestCycleListener() {
public void onRequestHandlerResolved(RequestCycle cycle, IRequestHandler handler) {
if (handler instanceof IPageRequestHandler) {
HttpServletRequest request = (HttpServletRequest) cycle.getRequest().getContainerRequest();
String sessionId = request.getRequestedSessionId();
//check whether the requested session has expired
boolean expired = sessionId != null && !request.isRequestedSessionIdValid();
//if session is not valid and it was really expired
if (expired && unboundSessions.contains(sessionId)) {
//then remove it from unbound list
unboundSessions.remove(sessionId);
//and throw exception
throw new PageExpiredException("Expired");
}
}
super.onRequestHandlerResolved(cycle, handler);
}
});
...
}
//this method called when any session is invalidated, so check your manual invalidating calls (if you ever do them)
#Override
public void sessionUnbound(String sessionId) {
super.sessionUnbound(sessionId);
if (!unboundSessions.contains(sessionId)) {
unboundSessions.add(sessionId);
}
}
}
Unbound sessions list needs for us to know, that user's session is really expired, since the expired variable in our listener could be also true when user just openes our site after redeploy, for example. His session is taken from his cookies and it could be already expired, but that would be weird to redirect him to SessionExpiredPage immediately.
It looks like a workaround, but it should work.
In cases where the user has closed the browser and walked away, I want to log when the session times out. For example as a system induced log out as opposed to user request (I already have working and tested code to log a user requested logout).
Since the user isn't actively submitting requests (especially if it is just a matter of the now unused session timing out on the server) I don't think a filter is possible. Can this be done with a phase listener? If so can you provide some insight or a skeleton, or at least point me in the right direction on how this might be done.
My understanding is that the session on the server is still active until it times out or some other mechanism invalidates it. I am assuming therefore that a phase listener will also be able to tell if as part of your login method, you kill any existing session prior to the user logging in again with a fresh view, other machine, etc.
I am OK with research, but would like to at least start while pointed in the right direction. :)
On a second note: Is it possible to differentiate between a session time out and a view expired?
Thought I'd post the solution I ended up with based on the suggestions:
public class ElsSoulardSessionListener implements HttpSessionListener {
#EJB
private SessionLogger sessionLogger = new SessionLogger();
private SessionLogDTO sessionData = new SessionLogDTO();
private ElsDateTimeFunctions ts = new ElsDateTimeFunctions();
private static final Logger logger = Logger.getLogger(ElsSoulardSessionListener.class.getSimpleName());
#Override
public void sessionCreated(HttpSessionEvent se) {
// Nothing to do yet
}
#Override
public void sessionDestroyed(HttpSessionEvent se) {
logger.log(Level.FINE, "SESSION DESTROYED LISTENER");
HttpSession session = se.getSession();
finalizeUserSessionLog(session);
}
/**
* Utility method to finalize user's session log entry. Returns
* early if the session log isn't found or more than one is returned.
* #param session
*/
private void finalizeUserSessionLog(HttpSession session) {
String sessionId = session.getId();
LogoutReasonType logoutReason = (LogoutReasonType) session.getAttribute("logoutreason");
if (logoutReason == null) {
logoutReason = LogoutReasonType.SESSION_TIMEOUT;
}
try {
sessionData = sessionLogger.findBySessionId(sessionId);
} catch (NonexistentEntityException | UnexpectedResultSetSizeException ex) {
logger.log(Level.WARNING, " sessionDestroyed ", ex);
return;
}
Calendar now = ts.getUtcDateTimeAsCalendar();
sessionData.setLogoutTimestamp(now);
sessionData.setLogoutReason(logoutReason);
sessionLogger.update(sessionData);
}
}
If this helps you...
In our application we have extended HttpSessionListener and used sessionDestroyed method to log the event of session timeout.
and registered the same in web.xml as
<listener>
<listener-class>
com.ourpackage.OurHttpSessionListener
</listener-class>
</listener>
I believe which you only catch events of the servlet container through ServletFilters. PhaseListener only exist inside JSF "sessions", after servlet requests. Check the JSF life cycle to make sure. After, you can create another request for invalidate the "session" in JSF