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 * http://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><p> 31 * The contexted exception approach allows the exception to be created together with a 32 * list of context label-value pairs. This additional information is automatically included in 33 * the message and printed stack trace. 34 * </p><p> 35 * An unchecked version of this exception is provided by ContextedRuntimeException. 36 * </p> 37 * <p> 38 * To use this class write code as follows: 39 * </p> 40 * <pre> 41 * try { 42 * ... 43 * } catch (Exception e) { 44 * throw new ContextedException("Error posting account transaction", e) 45 * .addContextValue("Account Number", accountNumber) 46 * .addContextValue("Amount Posted", amountPosted) 47 * .addContextValue("Previous Balance", previousBalance); 48 * } 49 * } 50 * </pre> 51 * <p> 52 * or improve diagnose data at a higher level: 53 * </p> 54 * <pre> 55 * try { 56 * ... 57 * } catch (ContextedException e) { 58 * throw e.setContextValue("Transaction Id", transactionId); 59 * } catch (Exception e) { 60 * if (e instanceof ExceptionContext) { 61 * e.setContextValue("Transaction Id", transactionId); 62 * } 63 * throw e; 64 * } 65 * } 66 * </pre> 67 * <p> 68 * The output in a printStacktrace() (which often is written to a log) would look something like the following: 69 * </p> 70 * <pre> 71 * org.apache.commons.lang3.exception.ContextedException: java.lang.Exception: Error posting account transaction 72 * Exception Context: 73 * [1:Account Number=null] 74 * [2:Amount Posted=100.00] 75 * [3:Previous Balance=-2.17] 76 * [4:Transaction Id=94ef1d15-d443-46c4-822b-637f26244899] 77 * 78 * --------------------------------- 79 * at org.apache.commons.lang3.exception.ContextedExceptionTest.testAddValue(ContextedExceptionTest.java:88) 80 * ..... (rest of trace) 81 * </pre> 82 * 83 * @see ContextedRuntimeException 84 * @since 3.0 85 */ 86 public class ContextedException extends Exception implements ExceptionContext { 87 88 /** The serialization version. */ 89 private static final long serialVersionUID = 20110706L; 90 /** The context where the data is stored. */ 91 private final ExceptionContext exceptionContext; 92 93 /** 94 * Instantiates ContextedException without message or cause. 95 * <p> 96 * The context information is stored using a default implementation. 97 */ ContextedException()98 public ContextedException() { 99 exceptionContext = new DefaultExceptionContext(); 100 } 101 102 /** 103 * Instantiates ContextedException with message, but without cause. 104 * <p> 105 * The context information is stored using a default implementation. 106 * 107 * @param message the exception message, may be null 108 */ ContextedException(final String message)109 public ContextedException(final String message) { 110 super(message); 111 exceptionContext = new DefaultExceptionContext(); 112 } 113 114 /** 115 * Instantiates ContextedException with cause, but without message. 116 * <p> 117 * The context information is stored using a default implementation. 118 * 119 * @param cause the underlying cause of the exception, may be null 120 */ ContextedException(final Throwable cause)121 public ContextedException(final Throwable cause) { 122 super(cause); 123 exceptionContext = new DefaultExceptionContext(); 124 } 125 126 /** 127 * Instantiates ContextedException with cause and message. 128 * <p> 129 * The context information is stored using a default implementation. 130 * 131 * @param message the exception message, may be null 132 * @param cause the underlying cause of the exception, may be null 133 */ ContextedException(final String message, final Throwable cause)134 public ContextedException(final String message, final Throwable cause) { 135 super(message, cause); 136 exceptionContext = new DefaultExceptionContext(); 137 } 138 139 /** 140 * Instantiates ContextedException with cause, message, and ExceptionContext. 141 * 142 * @param message the exception message, may be null 143 * @param cause the underlying cause of the exception, may be null 144 * @param context the context used to store the additional information, null uses default implementation 145 */ ContextedException(final String message, final Throwable cause, ExceptionContext context)146 public ContextedException(final String message, final Throwable cause, ExceptionContext context) { 147 super(message, cause); 148 if (context == null) { 149 context = new DefaultExceptionContext(); 150 } 151 exceptionContext = context; 152 } 153 154 /** 155 * Adds information helpful to a developer in diagnosing and correcting the problem. 156 * For the information to be meaningful, the value passed should have a reasonable 157 * toString() implementation. 158 * Different values can be added with the same label multiple times. 159 * <p> 160 * Note: This exception is only serializable if the object added is serializable. 161 * </p> 162 * 163 * @param label a textual label associated with information, {@code null} not recommended 164 * @param value information needed to understand exception, may be {@code null} 165 * @return {@code this}, for method chaining, not {@code null} 166 */ 167 @Override addContextValue(final String label, final Object value)168 public ContextedException addContextValue(final String label, final Object value) { 169 exceptionContext.addContextValue(label, value); 170 return this; 171 } 172 173 /** 174 * Sets information helpful to a developer in diagnosing and correcting the problem. 175 * For the information to be meaningful, the value passed should have a reasonable 176 * toString() implementation. 177 * Any existing values with the same labels are removed before the new one is added. 178 * <p> 179 * Note: This exception is only serializable if the object added as value is serializable. 180 * </p> 181 * 182 * @param label a textual label associated with information, {@code null} not recommended 183 * @param value information needed to understand exception, may be {@code null} 184 * @return {@code this}, for method chaining, not {@code null} 185 */ 186 @Override setContextValue(final String label, final Object value)187 public ContextedException setContextValue(final String label, final Object value) { 188 exceptionContext.setContextValue(label, value); 189 return this; 190 } 191 192 /** 193 * {@inheritDoc} 194 */ 195 @Override getContextValues(final String label)196 public List<Object> getContextValues(final String label) { 197 return this.exceptionContext.getContextValues(label); 198 } 199 200 /** 201 * {@inheritDoc} 202 */ 203 @Override getFirstContextValue(final String label)204 public Object getFirstContextValue(final String label) { 205 return this.exceptionContext.getFirstContextValue(label); 206 } 207 208 /** 209 * {@inheritDoc} 210 */ 211 @Override getContextEntries()212 public List<Pair<String, Object>> getContextEntries() { 213 return this.exceptionContext.getContextEntries(); 214 } 215 216 /** 217 * {@inheritDoc} 218 */ 219 @Override getContextLabels()220 public Set<String> getContextLabels() { 221 return exceptionContext.getContextLabels(); 222 } 223 224 /** 225 * Provides the message explaining the exception, including the contextual data. 226 * 227 * @see Throwable#getMessage() 228 * @return the message, never null 229 */ 230 @Override getMessage()231 public String getMessage() { 232 return getFormattedExceptionMessage(super.getMessage()); 233 } 234 235 /** 236 * Provides the message explaining the exception without the contextual data. 237 * 238 * @see Throwable#getMessage() 239 * @return the message 240 * @since 3.0.1 241 */ getRawMessage()242 public String getRawMessage() { 243 return super.getMessage(); 244 } 245 246 /** 247 * {@inheritDoc} 248 */ 249 @Override getFormattedExceptionMessage(final String baseMessage)250 public String getFormattedExceptionMessage(final String baseMessage) { 251 return exceptionContext.getFormattedExceptionMessage(baseMessage); 252 } 253 } 254