1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.lang3.exception;
18
19 import java.util.List;
20 import java.util.Set;
21
22 import org.apache.commons.lang3.tuple.Pair;
23
24 /**
25 * An exception that provides an easy and safe way to add contextual information.
26 * <p>
27 * An exception trace itself is often insufficient to provide rapid diagnosis of the issue.
28 * Frequently what is needed is a select few pieces of local contextual data.
29 * Providing this data is tricky however, due to concerns over formatting and nulls.
30 * </p>
31 * <p>
32 * The contexted exception approach allows the exception to be created together with a
33 * list of context label-value pairs. This additional information is automatically included in
34 * the message and printed stack trace.
35 * </p>
36 * <p>
37 * An unchecked version of this exception is provided by ContextedRuntimeException.
38 * </p>
39 * <p>
40 * To use this class write code as follows:
41 * </p>
42 * <pre>
43 * try {
44 * ...
45 * } catch (Exception e) {
46 * throw new ContextedException("Error posting account transaction", e)
47 * .addContextValue("Account Number", accountNumber)
48 * .addContextValue("Amount Posted", amountPosted)
49 * .addContextValue("Previous Balance", previousBalance);
50 * }
51 * }
52 * </pre>
53 * <p>
54 * or improve diagnose data at a higher level:
55 * </p>
56 * <pre>
57 * try {
58 * ...
59 * } catch (ContextedException e) {
60 * throw e.setContextValue("Transaction Id", transactionId);
61 * } catch (Exception e) {
62 * if (e instanceof ExceptionContext) {
63 * e.setContextValue("Transaction Id", transactionId);
64 * }
65 * throw e;
66 * }
67 * }
68 * </pre>
69 * <p>
70 * The output in a printStacktrace() (which often is written to a log) would look something like the following:
71 * </p>
72 * <pre>
73 * org.apache.commons.lang3.exception.ContextedException: java.lang.Exception: Error posting account transaction
74 * Exception Context:
75 * [1:Account Number=null]
76 * [2:Amount Posted=100.00]
77 * [3:Previous Balance=-2.17]
78 * [4:Transaction Id=94ef1d15-d443-46c4-822b-637f26244899]
79 *
80 * ---------------------------------
81 * at org.apache.commons.lang3.exception.ContextedExceptionTest.testAddValue(ContextedExceptionTest.java:88)
82 * ..... (rest of trace)
83 * </pre>
84 *
85 * @see ContextedRuntimeException
86 * @since 3.0
87 */
88 public class ContextedException extends Exception implements ExceptionContext {
89
90 /** The serialization version. */
91 private static final long serialVersionUID = 20110706L;
92
93 /** The context where the data is stored. */
94 private final ExceptionContext exceptionContext;
95
96 /**
97 * Instantiates ContextedException without message or cause.
98 * <p>
99 * The context information is stored using a default implementation.
100 */
101 public ContextedException() {
102 exceptionContext = new DefaultExceptionContext();
103 }
104
105 /**
106 * Instantiates ContextedException with message, but without cause.
107 * <p>
108 * The context information is stored using a default implementation.
109 *
110 * @param message The exception message, may be null
111 */
112 public ContextedException(final String message) {
113 super(message);
114 exceptionContext = new DefaultExceptionContext();
115 }
116
117 /**
118 * Instantiates ContextedException with cause and message.
119 * <p>
120 * The context information is stored using a default implementation.
121 *
122 * @param message The exception message, may be null
123 * @param cause The underlying cause of the exception, may be null
124 */
125 public ContextedException(final String message, final Throwable cause) {
126 super(message, cause);
127 exceptionContext = new DefaultExceptionContext();
128 }
129
130 /**
131 * Instantiates ContextedException with cause, message, and ExceptionContext.
132 *
133 * @param message The exception message, may be null
134 * @param cause The underlying cause of the exception, may be null
135 * @param context The context used to store the additional information, null uses default implementation
136 */
137 public ContextedException(final String message, final Throwable cause, ExceptionContext context) {
138 super(message, cause);
139 if (context == null) {
140 context = new DefaultExceptionContext();
141 }
142 exceptionContext = context;
143 }
144
145 /**
146 * Instantiates ContextedException with cause, but without message.
147 * <p>
148 * The context information is stored using a default implementation.
149 *
150 * @param cause The underlying cause of the exception, may be null
151 */
152 public ContextedException(final Throwable cause) {
153 super(cause);
154 exceptionContext = new DefaultExceptionContext();
155 }
156
157 /**
158 * Adds information helpful to a developer in diagnosing and correcting the problem.
159 * For the information to be meaningful, the value passed should have a reasonable
160 * toString() implementation.
161 * Different values can be added with the same label multiple times.
162 * <p>
163 * Note: This exception is only serializable if the object added is serializable.
164 * </p>
165 *
166 * @param label A textual label associated with information, {@code null} not recommended
167 * @param value information needed to understand exception, may be {@code null}
168 * @return {@code this}, for method chaining, not {@code null}
169 */
170 @Override
171 public ContextedException addContextValue(final String label, final Object value) {
172 exceptionContext.addContextValue(label, value);
173 return this;
174 }
175
176 /**
177 * {@inheritDoc}
178 */
179 @Override
180 public List<Pair<String, Object>> getContextEntries() {
181 return this.exceptionContext.getContextEntries();
182 }
183
184 /**
185 * {@inheritDoc}
186 */
187 @Override
188 public Set<String> getContextLabels() {
189 return exceptionContext.getContextLabels();
190 }
191
192 /**
193 * {@inheritDoc}
194 */
195 @Override
196 public List<Object> getContextValues(final String label) {
197 return this.exceptionContext.getContextValues(label);
198 }
199
200 /**
201 * {@inheritDoc}
202 */
203 @Override
204 public Object getFirstContextValue(final String label) {
205 return this.exceptionContext.getFirstContextValue(label);
206 }
207
208 /**
209 * {@inheritDoc}
210 */
211 @Override
212 public String getFormattedExceptionMessage(final String baseMessage) {
213 return exceptionContext.getFormattedExceptionMessage(baseMessage);
214 }
215
216 /**
217 * Gets the message explaining the exception, including the contextual data.
218 *
219 * @see Throwable#getMessage()
220 * @return The message, never null
221 */
222 @Override
223 public String getMessage() {
224 return getFormattedExceptionMessage(super.getMessage());
225 }
226
227 /**
228 * Gets the message explaining the exception without the contextual data.
229 *
230 * @see Throwable#getMessage()
231 * @return The message
232 * @since 3.0.1
233 */
234 public String getRawMessage() {
235 return super.getMessage();
236 }
237
238 /**
239 * Sets information helpful to a developer in diagnosing and correcting the problem.
240 * For the information to be meaningful, the value passed should have a reasonable
241 * toString() implementation.
242 * Any existing values with the same labels are removed before the new one is added.
243 * <p>
244 * Note: This exception is only serializable if the object added as value is serializable.
245 * </p>
246 *
247 * @param label A textual label associated with information, {@code null} not recommended
248 * @param value information needed to understand exception, may be {@code null}
249 * @return {@code this}, for method chaining, not {@code null}
250 */
251 @Override
252 public ContextedException setContextValue(final String label, final Object value) {
253 exceptionContext.setContextValue(label, value);
254 return this;
255 }
256 }