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 * A runtime 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> 031 * <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> 036 * <p> 037 * A checked version of this exception is provided by ContextedException. 038 * </p> 039 * <p> 040 * To use this class write code as follows: 041 * </p> 042 * <pre> 043 * try { 044 * ... 045 * } catch (Exception e) { 046 * throw new ContextedRuntimeException("Error posting account transaction", e) 047 * .addContextValue("Account Number", accountNumber) 048 * .addContextValue("Amount Posted", amountPosted) 049 * .addContextValue("Previous Balance", previousBalance); 050 * } 051 * } 052 * </pre> 053 * <p> 054 * or improve diagnose data at a higher level: 055 * </p> 056 * <pre> 057 * try { 058 * ... 059 * } catch (ContextedRuntimeException e) { 060 * throw e.setContextValue("Transaction Id", transactionId); 061 * } catch (Exception e) { 062 * if (e instanceof ExceptionContext) { 063 * e.setContextValue("Transaction Id", transactionId); 064 * } 065 * throw e; 066 * } 067 * } 068 * </pre> 069 * <p> 070 * The output in a printStacktrace() (which often is written to a log) would look something like the following: 071 * </p> 072 * <pre> 073 * org.apache.commons.lang3.exception.ContextedRuntimeException: java.lang.Exception: Error posting account transaction 074 * Exception Context: 075 * [1:Account Number=null] 076 * [2:Amount Posted=100.00] 077 * [3:Previous Balance=-2.17] 078 * [4:Transaction Id=94ef1d15-d443-46c4-822b-637f26244899] 079 * 080 * --------------------------------- 081 * at org.apache.commons.lang3.exception.ContextedRuntimeExceptionTest.testAddValue(ContextedExceptionTest.java:88) 082 * ..... (rest of trace) 083 * </pre> 084 * 085 * @see ContextedException 086 * @since 3.0 087 */ 088public class ContextedRuntimeException extends RuntimeException implements ExceptionContext { 089 090 /** The serialization version. */ 091 private static final long serialVersionUID = 20110706L; 092 093 /** The context where the data is stored. */ 094 private final ExceptionContext exceptionContext; 095 096 /** 097 * Instantiates ContextedRuntimeException without message or cause. 098 * <p> 099 * The context information is stored using a default implementation. 100 */ 101 public ContextedRuntimeException() { 102 exceptionContext = new DefaultExceptionContext(); 103 } 104 105 /** 106 * Instantiates ContextedRuntimeException 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 ContextedRuntimeException(final String message) { 113 super(message); 114 exceptionContext = new DefaultExceptionContext(); 115 } 116 117 /** 118 * Instantiates ContextedRuntimeException 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 ContextedRuntimeException(final String message, final Throwable cause) { 126 super(message, cause); 127 exceptionContext = new DefaultExceptionContext(); 128 } 129 130 /** 131 * Instantiates ContextedRuntimeException 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 ContextedRuntimeException(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 ContextedRuntimeException 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 ContextedRuntimeException(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 ContextedRuntimeException 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 ContextedRuntimeException setContextValue(final String label, final Object value) { 253 exceptionContext.setContextValue(label, value); 254 return this; 255 } 256 257}