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 * http://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 * <p> 026 * A runtime exception that provides an easy and safe way to add contextual information. 027 * </p><p> 028 * An exception trace itself is often insufficient to provide rapid diagnosis of the issue. 029 * Frequently what is needed is a select few pieces of local contextual data. 030 * Providing this data is tricky however, due to concerns over formatting and nulls. 031 * </p><p> 032 * The contexted exception approach allows the exception to be created together with a 033 * list of context label-value pairs. This additional information is automatically included in 034 * the message and printed stack trace. 035 * </p><p> 036 * A checked version of this exception is provided by ContextedException. 037 * </p> 038 * <p> 039 * To use this class write code as follows: 040 * </p> 041 * <pre> 042 * try { 043 * ... 044 * } catch (Exception e) { 045 * throw new ContextedRuntimeException("Error posting account transaction", e) 046 * .addContextValue("Account Number", accountNumber) 047 * .addContextValue("Amount Posted", amountPosted) 048 * .addContextValue("Previous Balance", previousBalance) 049 * } 050 * } 051 * </pre> 052 * <p> 053 * or improve diagnose data at a higher level: 054 * </p> 055 * <pre> 056 * try { 057 * ... 058 * } catch (ContextedRuntimeException e) { 059 * throw e.setContextValue("Transaction Id", transactionId); 060 * } catch (Exception e) { 061 * if (e instanceof ExceptionContext) { 062 * e.setContextValue("Transaction Id", transactionId); 063 * } 064 * throw e; 065 * } 066 * } 067 * </pre> 068 * <p> 069 * The output in a printStacktrace() (which often is written to a log) would look something like the following: 070 * </p> 071 * <pre> 072 * org.apache.commons.lang3.exception.ContextedRuntimeException: java.lang.Exception: Error posting account transaction 073 * Exception Context: 074 * [1:Account Number=null] 075 * [2:Amount Posted=100.00] 076 * [3:Previous Balance=-2.17] 077 * [4:Transaction Id=94ef1d15-d443-46c4-822b-637f26244899] 078 * 079 * --------------------------------- 080 * at org.apache.commons.lang3.exception.ContextedRuntimeExceptionTest.testAddValue(ContextedExceptionTest.java:88) 081 * ..... (rest of trace) 082 * </pre> 083 * 084 * @see ContextedException 085 * @since 3.0 086 */ 087public class ContextedRuntimeException extends RuntimeException implements ExceptionContext { 088 089 /** The serialization version. */ 090 private static final long serialVersionUID = 20110706L; 091 /** The context where the data is stored. */ 092 private final ExceptionContext exceptionContext; 093 094 /** 095 * Instantiates ContextedRuntimeException without message or cause. 096 * <p> 097 * The context information is stored using a default implementation. 098 */ 099 public ContextedRuntimeException() { 100 super(); 101 exceptionContext = new DefaultExceptionContext(); 102 } 103 104 /** 105 * Instantiates ContextedRuntimeException with message, but without cause. 106 * <p> 107 * The context information is stored using a default implementation. 108 * 109 * @param message the exception message, may be null 110 */ 111 public ContextedRuntimeException(final String message) { 112 super(message); 113 exceptionContext = new DefaultExceptionContext(); 114 } 115 116 /** 117 * Instantiates ContextedRuntimeException with cause, but without message. 118 * <p> 119 * The context information is stored using a default implementation. 120 * 121 * @param cause the underlying cause of the exception, may be null 122 */ 123 public ContextedRuntimeException(final Throwable cause) { 124 super(cause); 125 exceptionContext = new DefaultExceptionContext(); 126 } 127 128 /** 129 * Instantiates ContextedRuntimeException with cause and message. 130 * <p> 131 * The context information is stored using a default implementation. 132 * 133 * @param message the exception message, may be null 134 * @param cause the underlying cause of the exception, may be null 135 */ 136 public ContextedRuntimeException(final String message, final Throwable cause) { 137 super(message, cause); 138 exceptionContext = new DefaultExceptionContext(); 139 } 140 141 /** 142 * Instantiates ContextedRuntimeException with cause, message, and ExceptionContext. 143 * 144 * @param message the exception message, may be null 145 * @param cause the underlying cause of the exception, may be null 146 * @param context the context used to store the additional information, null uses default implementation 147 */ 148 public ContextedRuntimeException(final String message, final Throwable cause, ExceptionContext context) { 149 super(message, cause); 150 if (context == null) { 151 context = new DefaultExceptionContext(); 152 } 153 exceptionContext = context; 154 } 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 ContextedRuntimeException addContextValue(final String label, final Object value) { 172 exceptionContext.addContextValue(label, value); 173 return this; 174 } 175 176 /** 177 * Sets information helpful to a developer in diagnosing and correcting the problem. 178 * For the information to be meaningful, the value passed should have a reasonable 179 * toString() implementation. 180 * Any existing values with the same labels are removed before the new one is added. 181 * <p> 182 * Note: This exception is only serializable if the object added as value is serializable. 183 * </p> 184 * 185 * @param label a textual label associated with information, {@code null} not recommended 186 * @param value information needed to understand exception, may be {@code null} 187 * @return {@code this}, for method chaining, not {@code null} 188 */ 189 @Override 190 public ContextedRuntimeException setContextValue(final String label, final Object value) { 191 exceptionContext.setContextValue(label, value); 192 return this; 193 } 194 195 /** 196 * {@inheritDoc} 197 */ 198 @Override 199 public List<Object> getContextValues(final String label) { 200 return this.exceptionContext.getContextValues(label); 201 } 202 203 /** 204 * {@inheritDoc} 205 */ 206 @Override 207 public Object getFirstContextValue(final String label) { 208 return this.exceptionContext.getFirstContextValue(label); 209 } 210 211 /** 212 * {@inheritDoc} 213 */ 214 @Override 215 public List<Pair<String, Object>> getContextEntries() { 216 return this.exceptionContext.getContextEntries(); 217 } 218 219 /** 220 * {@inheritDoc} 221 */ 222 @Override 223 public Set<String> getContextLabels() { 224 return exceptionContext.getContextLabels(); 225 } 226 227 /** 228 * Provides the message explaining the exception, including the contextual data. 229 * 230 * @see java.lang.Throwable#getMessage() 231 * @return the message, never null 232 */ 233 @Override 234 public String getMessage() { 235 return getFormattedExceptionMessage(super.getMessage()); 236 } 237 238 /** 239 * Provides the message explaining the exception without the contextual data. 240 * 241 * @see java.lang.Throwable#getMessage() 242 * @return the message 243 * @since 3.0.1 244 */ 245 public String getRawMessage() { 246 return super.getMessage(); 247 } 248 249 /** 250 * {@inheritDoc} 251 */ 252 @Override 253 public String getFormattedExceptionMessage(final String baseMessage) { 254 return exceptionContext.getFormattedExceptionMessage(baseMessage); 255 } 256 257}