1 /*
2  * JBoss, Home of Professional Open Source.
3  * Copyright 2014 Red Hat, Inc., and individual contributors
4  * as indicated by the @author tags.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *  Unless required by applicable law or agreed to in writing, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License.
17  */

18
19 package io.undertow.servlet.api;
20
21 import io.undertow.UndertowLogger;
22 import io.undertow.server.HttpServerExchange;
23 import io.undertow.servlet.ExceptionLog;
24 import org.jboss.logging.BasicLogger;
25 import org.jboss.logging.Logger;
26
27 import java.io.IOException;
28 import java.util.Collections;
29 import java.util.HashMap;
30 import java.util.Map;
31 import javax.servlet.ServletRequest;
32 import javax.servlet.ServletResponse;
33
34 /**
35  * An exception handler that
36  *
37  *
38  * @author Stuart Douglas
39  */

40 public class LoggingExceptionHandler implements ExceptionHandler {
41
42     public static final LoggingExceptionHandler DEFAULT = new LoggingExceptionHandler(Collections.<Class<? extends Throwable>, ExceptionDetails>emptyMap());
43
44     private final Map<Class<? extends Throwable>, ExceptionDetails> exceptionDetails;
45
46     public LoggingExceptionHandler(Map<Class<? extends Throwable>, ExceptionDetails> exceptionDetails) {
47         this.exceptionDetails = exceptionDetails;
48     }
49
50     @Override
51     public boolean handleThrowable(HttpServerExchange exchange, ServletRequest request, ServletResponse response, Throwable t) {
52         ExceptionDetails details = null;
53         if (!exceptionDetails.isEmpty()) {
54             Class c = t.getClass();
55             while (c != null && c != Object.class) {
56                 details = exceptionDetails.get(c);
57                 if (details != null) {
58                     break;
59                 }
60                 c = c.getSuperclass();
61             }
62         }
63
64         ExceptionLog log = t.getClass().getAnnotation(ExceptionLog.class);
65         if (details != null) {
66             Logger.Level level = details.level;
67             Logger.Level stackTraceLevel = details.stackTraceLevel;
68             String category = details.category;
69             handleCustomLog(exchange, t, level, stackTraceLevel, category);
70         } else if (log != null) {
71             Logger.Level level = log.value();
72             Logger.Level stackTraceLevel = log.stackTraceLevel();
73             String category = log.category();
74             handleCustomLog(exchange, t, level, stackTraceLevel, category);
75         } else if (t instanceof IOException) {
76             //we log IOExceptions at a lower level
77             //because they can be easily caused by malicious remote clients in at attempt to DOS the server by filling the logs
78             UndertowLogger.REQUEST_IO_LOGGER.debugf(t, "Exception handling request to %s", exchange.getRequestURI());
79         } else {
80             UndertowLogger.REQUEST_LOGGER.exceptionHandlingRequest(t, exchange.getRequestURI());
81         }
82         return false;
83     }
84
85     private void handleCustomLog(HttpServerExchange exchange, Throwable t, Logger.Level level, Logger.Level stackTraceLevel, String category) {
86         BasicLogger logger = UndertowLogger.REQUEST_LOGGER;
87         if (!category.isEmpty()) {
88             logger = Logger.getLogger(category);
89         }
90         boolean stackTrace = true;
91         if (stackTraceLevel.ordinal() > level.ordinal()) {
92             if (!logger.isEnabled(stackTraceLevel)) {
93                 stackTrace = false;
94             }
95         }
96         if (stackTrace) {
97             logger.logf(level, t, "Exception handling request to %s", exchange.getRequestURI());
98         } else {
99             logger.logf(level, "Exception handling request to %s: %s", exchange.getRequestURI(), t.getMessage());
100         }
101     }
102
103
104     private static class ExceptionDetails {
105
106         final Logger.Level level;
107
108         final Logger.Level stackTraceLevel;
109
110         final String category;
111
112         private ExceptionDetails(Logger.Level level, Logger.Level stackTraceLevel, String category) {
113             this.level = level;
114             this.stackTraceLevel = stackTraceLevel;
115             this.category = category;
116         }
117     }
118
119     public static Builder builder() {
120         return new Builder();
121     }
122
123     public static final class Builder {
124         private final Map<Class<? extends Throwable>, ExceptionDetails> exceptionDetails = new HashMap<>();
125
126         Builder() {}
127
128         public Builder add(Class<? extends Throwable> exception, String category, Logger.Level level) {
129             exceptionDetails.put(exception, new ExceptionDetails(level, Logger.Level.FATAL, category));
130             return this;
131         }
132         public Builder add(Class<? extends Throwable> exception, String category) {
133             exceptionDetails.put(exception, new ExceptionDetails(Logger.Level.ERROR, Logger.Level.FATAL, category));
134             return this;
135         }
136         public Builder add(Class<? extends Throwable> exception, String category, Logger.Level level, Logger.Level stackTraceLevel) {
137             exceptionDetails.put(exception, new ExceptionDetails(level, stackTraceLevel, category));
138             return this;
139         }
140
141         public LoggingExceptionHandler build() {
142             return new LoggingExceptionHandler(exceptionDetails);
143         }
144     }
145 }
146