001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.lang3.exception; 018 019import java.util.List; 020import java.util.Set; 021 022import org.apache.commons.lang3.tuple.Pair; 023 024/** 025 * An exception that provides an easy and safe way to add contextual information. 026 * <p> 027 * An exception trace itself is often insufficient to provide rapid diagnosis of the issue. 028 * Frequently what is needed is a select few pieces of local contextual data. 029 * Providing this data is tricky however, due to concerns over formatting and nulls. 030 * </p><p> 031 * The contexted exception approach allows the exception to be created together with a 032 * list of context label-value pairs. This additional information is automatically included in 033 * the message and printed stack trace. 034 * </p><p> 035 * An unchecked version of this exception is provided by ContextedRuntimeException. 036 * </p> 037 * <p> 038 * To use this class write code as follows: 039 * </p> 040 * <pre> 041 * try { 042 * ... 043 * } catch (Exception e) { 044 * throw new ContextedException("Error posting account transaction", e) 045 * .addContextValue("Account Number", accountNumber) 046 * .addContextValue("Amount Posted", amountPosted) 047 * .addContextValue("Previous Balance", previousBalance); 048 * } 049 * } 050 * </pre> 051 * <p> 052 * or improve diagnose data at a higher level: 053 * </p> 054 * <pre> 055 * try { 056 * ... 057 * } catch (ContextedException e) { 058 * throw e.setContextValue("Transaction Id", transactionId); 059 * } catch (Exception e) { 060 * if (e instanceof ExceptionContext) { 061 * e.setContextValue("Transaction Id", transactionId); 062 * } 063 * throw e; 064 * } 065 * } 066 * </pre> 067 * <p> 068 * The output in a printStacktrace() (which often is written to a log) would look something like the following: 069 * </p> 070 * <pre> 071 * org.apache.commons.lang3.exception.ContextedException: java.lang.Exception: Error posting account transaction 072 * Exception Context: 073 * [1:Account Number=null] 074 * [2:Amount Posted=100.00] 075 * [3:Previous Balance=-2.17] 076 * [4:Transaction Id=94ef1d15-d443-46c4-822b-637f26244899] 077 * 078 * --------------------------------- 079 * at org.apache.commons.lang3.exception.ContextedExceptionTest.testAddValue(ContextedExceptionTest.java:88) 080 * ..... (rest of trace) 081 * </pre> 082 * 083 * @see ContextedRuntimeException 084 * @since 3.0 085 */ 086public class ContextedException extends Exception implements ExceptionContext { 087 088 /** The serialization version. */ 089 private static final long serialVersionUID = 20110706L; 090 091 /** The context where the data is stored. */ 092 private final ExceptionContext exceptionContext; 093 094 /** 095 * Instantiates ContextedException without message or cause. 096 * <p> 097 * The context information is stored using a default implementation. 098 */ 099 public ContextedException() { 100 exceptionContext = new DefaultExceptionContext(); 101 } 102 103 /** 104 * Instantiates ContextedException with message, but without cause. 105 * <p> 106 * The context information is stored using a default implementation. 107 * 108 * @param message the exception message, may be null 109 */ 110 public ContextedException(final String message) { 111 super(message); 112 exceptionContext = new DefaultExceptionContext(); 113 } 114 115 /** 116 * Instantiates ContextedException with cause and message. 117 * <p> 118 * The context information is stored using a default implementation. 119 * 120 * @param message the exception message, may be null 121 * @param cause the underlying cause of the exception, may be null 122 */ 123 public ContextedException(final String message, final Throwable cause) { 124 super(message, cause); 125 exceptionContext = new DefaultExceptionContext(); 126 } 127 128 /** 129 * Instantiates ContextedException with cause, message, and ExceptionContext. 130 * 131 * @param message the exception message, may be null 132 * @param cause the underlying cause of the exception, may be null 133 * @param context the context used to store the additional information, null uses default implementation 134 */ 135 public ContextedException(final String message, final Throwable cause, ExceptionContext context) { 136 super(message, cause); 137 if (context == null) { 138 context = new DefaultExceptionContext(); 139 } 140 exceptionContext = context; 141 } 142 143 /** 144 * Instantiates ContextedException with cause, but without message. 145 * <p> 146 * The context information is stored using a default implementation. 147 * 148 * @param cause the underlying cause of the exception, may be null 149 */ 150 public ContextedException(final Throwable cause) { 151 super(cause); 152 exceptionContext = new DefaultExceptionContext(); 153 } 154 155 /** 156 * Adds information helpful to a developer in diagnosing and correcting the problem. 157 * For the information to be meaningful, the value passed should have a reasonable 158 * toString() implementation. 159 * Different values can be added with the same label multiple times. 160 * <p> 161 * Note: This exception is only serializable if the object added is serializable. 162 * </p> 163 * 164 * @param label a textual label associated with information, {@code null} not recommended 165 * @param value information needed to understand exception, may be {@code null} 166 * @return {@code this}, for method chaining, not {@code null} 167 */ 168 @Override 169 public ContextedException addContextValue(final String label, final Object value) { 170 exceptionContext.addContextValue(label, value); 171 return this; 172 } 173 174 /** 175 * {@inheritDoc} 176 */ 177 @Override 178 public List<Pair<String, Object>> getContextEntries() { 179 return this.exceptionContext.getContextEntries(); 180 } 181 182 /** 183 * {@inheritDoc} 184 */ 185 @Override 186 public Set<String> getContextLabels() { 187 return exceptionContext.getContextLabels(); 188 } 189 190 /** 191 * {@inheritDoc} 192 */ 193 @Override 194 public List<Object> getContextValues(final String label) { 195 return this.exceptionContext.getContextValues(label); 196 } 197 198 /** 199 * {@inheritDoc} 200 */ 201 @Override 202 public Object getFirstContextValue(final String label) { 203 return this.exceptionContext.getFirstContextValue(label); 204 } 205 206 /** 207 * {@inheritDoc} 208 */ 209 @Override 210 public String getFormattedExceptionMessage(final String baseMessage) { 211 return exceptionContext.getFormattedExceptionMessage(baseMessage); 212 } 213 214 /** 215 * Provides the message explaining the exception, including the contextual data. 216 * 217 * @see Throwable#getMessage() 218 * @return the message, never null 219 */ 220 @Override 221 public String getMessage() { 222 return getFormattedExceptionMessage(super.getMessage()); 223 } 224 225 /** 226 * Provides the message explaining the exception without the contextual data. 227 * 228 * @see Throwable#getMessage() 229 * @return the message 230 * @since 3.0.1 231 */ 232 public String getRawMessage() { 233 return super.getMessage(); 234 } 235 236 /** 237 * Sets information helpful to a developer in diagnosing and correcting the problem. 238 * For the information to be meaningful, the value passed should have a reasonable 239 * toString() implementation. 240 * Any existing values with the same labels are removed before the new one is added. 241 * <p> 242 * Note: This exception is only serializable if the object added as value is serializable. 243 * </p> 244 * 245 * @param label a textual label associated with information, {@code null} not recommended 246 * @param value information needed to understand exception, may be {@code null} 247 * @return {@code this}, for method chaining, not {@code null} 248 */ 249 @Override 250 public ContextedException setContextValue(final String label, final Object value) { 251 exceptionContext.setContextValue(label, value); 252 return this; 253 } 254}