001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.rest; 014 015import static org.apache.juneau.rest.Enablement.*; 016 017import java.util.*; 018import java.util.logging.*; 019 020import javax.servlet.http.*; 021 022import org.apache.juneau.*; 023import org.apache.juneau.collections.*; 024import org.apache.juneau.json.*; 025 026/** 027 * Represents a set of logging rules for how to handle logging of HTTP requests/responses. 028 */ 029public class RestCallLoggerConfig { 030 031 /** 032 * Default empty logging config. 033 */ 034 public static final RestCallLoggerConfig DEFAULT_NOOP = RestCallLoggerConfig.create().build(); 035 036 /** 037 * Default debug logging config. 038 */ 039 public static final RestCallLoggerConfig DEFAULT_DEBUG = 040 RestCallLoggerConfig 041 .create() 042 .useStackTraceHashing(false) 043 .level(Level.WARNING) 044 .rules( 045 RestCallLoggerRule 046 .create() 047 .codes("*") 048 .verbose() 049 .build() 050 ) 051 .build(); 052 053 private final RestCallLoggerRule[] rules; 054 private final boolean useStackTraceHashing; 055 private final Enablement disabled; 056 private final int stackTraceHashingTimeout; 057 private final Level level; 058 059 RestCallLoggerConfig(Builder b) { 060 RestCallLoggerConfig p = b.parent; 061 062 this.disabled = b.disabled != null ? b.disabled : p != null ? p.disabled : FALSE; 063 this.useStackTraceHashing = b.useStackTraceHashing != null ? b.useStackTraceHashing : p != null ? p.useStackTraceHashing : false; 064 this.stackTraceHashingTimeout = b.stackTraceHashingTimeout != null ? b.stackTraceHashingTimeout : p != null ? p.stackTraceHashingTimeout : Integer.MAX_VALUE; 065 this.level = b.level != null ? b.level : p != null ? p.level : Level.INFO; 066 067 AList<RestCallLoggerRule> rules = AList.of(); 068 rules.addAll(b.rules); 069 if (p != null) 070 rules.a(p.rules); 071 this.rules = rules.asArrayOf(RestCallLoggerRule.class); 072 } 073 074 /** 075 * Creates a builder for this class. 076 * 077 * @return A new builder for this class. 078 */ 079 public static Builder create() { 080 return new Builder(); 081 } 082 083 /** 084 * Builder for {@link RestCallLoggerConfig} objects. 085 */ 086 public static class Builder { 087 List<RestCallLoggerRule> rules = new ArrayList<>(); 088 RestCallLoggerConfig parent; 089 Level level; 090 Boolean useStackTraceHashing; 091 Enablement disabled; 092 Integer stackTraceHashingTimeout; 093 094 /** 095 * Sets the parent logging config. 096 * 097 * @param parent 098 * The parent logging config. 099 * <br>Can be <jk>null</jk>. 100 * @return This object (for method chaining). 101 */ 102 public Builder parent(RestCallLoggerConfig parent) { 103 this.parent = parent; 104 return this; 105 } 106 107 /** 108 * Adds a new logging rule to this config. 109 * 110 * <p> 111 * The rule will be added to the END of list of current rules and thus checked last in the current list but 112 * before any parent rules. 113 * 114 * @param rule The logging rule to add to this config. 115 * @return This object (for method chaining). 116 */ 117 public Builder rule(RestCallLoggerRule rule) { 118 this.rules.add(rule); 119 return this; 120 } 121 122 /** 123 * Adds new logging rules to this config. 124 * 125 * <p> 126 * The rules will be added in order to the END of list of current rules and thus checked last in the current list but 127 * before any parent rules. 128 * 129 * @param rules The logging rules to add to this config. 130 * @return This object (for method chaining). 131 */ 132 public Builder rules(RestCallLoggerRule...rules) { 133 for (RestCallLoggerRule rule : rules) 134 this.rules.add(rule); 135 return this; 136 } 137 138 /** 139 * Enables no-trace mode on this config. 140 * 141 * <p> 142 * No-trace mode prevents logging of messages to the log file. 143 * 144 * <p> 145 * Possible values (case-insensitive): 146 * <ul> 147 * <li>{@link Enablement#TRUE TRUE} - No-trace mode enabled for all requests. 148 * <li>{@link Enablement#FALSE FALSE} - No-trace mode disabled for all requests. 149 * <li>{@link Enablement#PER_REQUEST PER_REQUEST} - No-trace mode enabled for requests that have a <js>"X-NoTrace: true"</js> header. 150 * </ul> 151 * 152 * @param value 153 * The value for this property. 154 * <br>Can be <jk>null</jk> (inherit from parent or default to {@link Enablement#FALSE NEVER}). 155 * @return This object (for method chaining). 156 */ 157 public Builder disabled(Enablement value) { 158 this.disabled = value; 159 return this; 160 } 161 162 /** 163 * Shortcut for calling <c>disabled(<jsf>TRUE</jsf>)</c>. 164 * 165 * @return This object (for method chaining). 166 */ 167 public Builder disabled() { 168 return disabled(TRUE); 169 } 170 171 /** 172 * Enables the use of stacktrace hashing. 173 * 174 * <p> 175 * When enabled, stacktraces will be replaced with hashes in the log file. 176 * 177 * @param value 178 * The value for this property. 179 * <br>Can be <jk>null</jk> (inherit from parent or default to <jk>false</jk>). 180 * @return This object (for method chaining). 181 */ 182 public Builder useStackTraceHashing(Boolean value) { 183 this.useStackTraceHashing = value; 184 return this; 185 } 186 187 /** 188 * Shortcut for calling <c>useStackTraceHashing(<jk>true</jk>);</c>. 189 * 190 * @return This object (for method chaining). 191 */ 192 public Builder useStackTraceHashing() { 193 this.useStackTraceHashing = true; 194 return this; 195 } 196 197 /** 198 * Enables a timeout after which stack traces hashes are flushed. 199 * 200 * @param timeout 201 * Time in milliseconds to hash stack traces for. 202 * <br>Can be <jk>null</jk> (inherit from parent or default to {@link Integer#MAX_VALUE MAX_VALUE}). 203 * @return This object (for method chaining). 204 */ 205 public Builder stackTraceHashingTimeout(Integer timeout) { 206 this.stackTraceHashingTimeout = timeout; 207 return this; 208 } 209 210 /** 211 * The default logging level. 212 * 213 * <p> 214 * This defines the logging level for messages if they're not already defined on the matched rule. 215 * 216 * @param value 217 * The value for this property. 218 * <br>Can be <jk>null</jk> (inherit from parent or default to {@link Level#INFO INFO}). 219 * @return This object (for method chaining). 220 */ 221 public Builder level(Level value) { 222 this.level = value; 223 return this; 224 } 225 226 /** 227 * Applies the properties in the specified object map to this builder. 228 * 229 * @param m The map containing properties to apply. 230 * @return This object (for method chaining). 231 */ 232 public Builder apply(OMap m) { 233 for (String key : m.keySet()) { 234 if ("useStackTraceHashing".equals(key)) 235 useStackTraceHashing(m.getBoolean("useStackTraceHashing")); 236 else if ("stackTraceHashingTimeout".equals(key)) 237 stackTraceHashingTimeout(m.getInt("stackTraceHashingTimeout")); 238 else if ("disabled".equals(key)) 239 disabled(m.get("disabled", Enablement.class)); 240 else if ("rules".equals(key)) 241 rules(m.get("rules", RestCallLoggerRule[].class)); 242 else if ("level".equals(key)) 243 level(m.get("level", Level.class)); 244 } 245 return this; 246 } 247 248 /** 249 * Creates the {@link RestCallLoggerConfig} object based on settings on this builder. 250 * 251 * @return A new {@link RestCallLoggerConfig} object. 252 */ 253 public RestCallLoggerConfig build() { 254 return new RestCallLoggerConfig(this); 255 } 256 } 257 258 /** 259 * Given the specified servlet request/response, find the rule that applies to it. 260 * 261 * @param req The servlet request. 262 * @param res The servlet response. 263 * @return The applicable logging rule, or <jk>null</jk> if a match could not be found. 264 */ 265 public RestCallLoggerRule getRule(HttpServletRequest req, HttpServletResponse res) { 266 267 int status = res.getStatus(); 268 Throwable e = (Throwable)req.getAttribute("Exception"); 269 boolean debug = isDebug(req); 270 271 for (RestCallLoggerRule r : rules) { 272 if (r.matches(status, debug, e)) { 273 Enablement disabled = r.getDisabled(); 274 if (disabled == null) 275 disabled = this.disabled; 276 if (disabled == TRUE) 277 return null; 278 if (isNoTraceAttr(req)) 279 return null; 280 if (disabled == FALSE) 281 return r; 282 if (isNoTraceHeader(req)) 283 return null; 284 return r; 285 } 286 } 287 288 return null; 289 } 290 291 /** 292 * Returns <jk>true</jk> if logging is disabled for this request. 293 * 294 * @param req The HTTP request. 295 * @return <jk>true</jk> if logging is disabled for this request. 296 */ 297 public boolean isDisabled(HttpServletRequest req) { 298 if (disabled == TRUE) 299 return true; 300 if (disabled == FALSE) 301 return false; 302 return isNoTraceAttr(req); 303 } 304 305 private boolean isDebug(HttpServletRequest req) { 306 Boolean b = boolAttr(req, "Debug"); 307 return (b != null && b == true); 308 } 309 310 private boolean isNoTraceAttr(HttpServletRequest req) { 311 Boolean b = boolAttr(req, "NoTrace"); 312 return (b != null && b == true); 313 } 314 315 private boolean isNoTraceHeader(HttpServletRequest req) { 316 return "true".equalsIgnoreCase(req.getHeader("X-NoTrace")); 317 } 318 319 /** 320 * Returns the default logging level. 321 * 322 * @return The default logging level. 323 */ 324 public Level getLevel() { 325 return level; 326 } 327 328 /** 329 * Returns <jk>true</jk> if stack traces should be hashed. 330 * 331 * @return <jk>true</jk> if stack traces should be hashed. 332 */ 333 public boolean isUseStackTraceHashing() { 334 return useStackTraceHashing; 335 } 336 337 /** 338 * Returns the time in milliseconds that stacktrace hashes should be persisted. 339 * 340 * @return The time in milliseconds that stacktrace hashes should be persisted. 341 */ 342 public int getStackTraceHashingTimeout() { 343 return stackTraceHashingTimeout; 344 } 345 346 /** 347 * Returns the rules defined in this config. 348 * 349 * @return Thew rules defined in this config. 350 */ 351 public List<RestCallLoggerRule> getRules() { 352 return Collections.unmodifiableList(Arrays.asList(rules)); 353 } 354 355 //----------------------------------------------------------------------------------------------------------------- 356 // Other methods 357 //----------------------------------------------------------------------------------------------------------------- 358 359 private Boolean boolAttr(HttpServletRequest req, String name) { 360 Object o = req.getAttribute(name); 361 if (o == null || ! (o instanceof Boolean)) 362 return null; 363 return (Boolean)o; 364 } 365 366 @Override /* Object */ 367 public String toString() { 368 return SimpleJsonSerializer.DEFAULT_READABLE.toString(toMap()); 369 } 370 371 /** 372 * Returns the properties defined on this bean context as a simple map for debugging purposes. 373 * 374 * @return A new map containing the properties defined on this context. 375 */ 376 public OMap toMap() { 377 return new DefaultFilteringOMap() 378 .append("useStackTraceHashing", useStackTraceHashing) 379 .append("disabled", disabled == FALSE ? null : disabled) 380 .append("stackTraceHashingTimeout", stackTraceHashingTimeout == Integer.MAX_VALUE ? null : stackTraceHashingTimeout) 381 .append("level", level == Level.INFO ? null : level) 382 .append("rules", rules.length == 0 ? null : rules) 383 ; 384 } 385}