001/** 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved. 004 * 005 * This program and the accompanying materials are dual-licensed under 006 * either the terms of the Eclipse Public License v1.0 as published by 007 * the Eclipse Foundation 008 * 009 * or (per the licensee's choosing) 010 * 011 * under the terms of the GNU Lesser General Public License version 2.1 012 * as published by the Free Software Foundation. 013 */ 014package ch.qos.logback.access.tomcat; 015 016import java.io.File; 017import java.io.IOException; 018import java.net.MalformedURLException; 019import java.net.URL; 020import java.util.HashMap; 021import java.util.Iterator; 022import java.util.List; 023import java.util.Map; 024import java.util.concurrent.ExecutorService; 025import java.util.concurrent.ScheduledExecutorService; 026import java.util.concurrent.ScheduledFuture; 027 028import javax.servlet.ServletContext; 029import javax.servlet.ServletException; 030 031import org.apache.catalina.Lifecycle; 032import org.apache.catalina.LifecycleException; 033import org.apache.catalina.LifecycleListener; 034import org.apache.catalina.LifecycleState; 035import org.apache.catalina.connector.Request; 036import org.apache.catalina.connector.Response; 037import org.apache.catalina.valves.ValveBase; 038 039import ch.qos.logback.access.AccessConstants; 040import ch.qos.logback.access.joran.JoranConfigurator; 041import ch.qos.logback.access.spi.AccessEvent; 042import ch.qos.logback.access.spi.IAccessEvent; 043import ch.qos.logback.core.Appender; 044import ch.qos.logback.core.BasicStatusManager; 045import ch.qos.logback.core.Context; 046import ch.qos.logback.core.CoreConstants; 047import ch.qos.logback.core.LifeCycleManager; 048import ch.qos.logback.core.boolex.EventEvaluator; 049import ch.qos.logback.core.filter.Filter; 050import ch.qos.logback.core.joran.spi.JoranException; 051import ch.qos.logback.core.spi.AppenderAttachable; 052import ch.qos.logback.core.spi.AppenderAttachableImpl; 053import ch.qos.logback.core.spi.FilterAttachable; 054import ch.qos.logback.core.spi.FilterAttachableImpl; 055import ch.qos.logback.core.spi.FilterReply; 056import ch.qos.logback.core.spi.LifeCycle; 057import ch.qos.logback.core.spi.LogbackLock; 058import ch.qos.logback.core.spi.SequenceNumberGenerator; 059import ch.qos.logback.core.status.ErrorStatus; 060import ch.qos.logback.core.status.InfoStatus; 061import ch.qos.logback.core.status.OnConsoleStatusListener; 062import ch.qos.logback.core.status.Status; 063import ch.qos.logback.core.status.StatusManager; 064import ch.qos.logback.core.status.WarnStatus; 065import ch.qos.logback.core.util.ExecutorServiceUtil; 066import ch.qos.logback.core.util.Loader; 067import ch.qos.logback.core.util.OptionHelper; 068import ch.qos.logback.core.util.StatusListenerConfigHelper; 069 070//import org.apache.catalina.Lifecycle; 071 072/** 073 * This class is an implementation of tomcat's Valve interface, by extending 074 * ValveBase. 075 * 076 * <p> 077 * For more information on using LogbackValve please refer to the online 078 * documentation on 079 * <a href="http://logback.qos.ch/access.html#tomcat">logback-access and 080 * tomcat</a>. 081 * 082 * @author Ceki Gülcü 083 * @author Sébastien Pennec 084 */ 085public class LogbackValve extends ValveBase 086 implements Lifecycle, Context, AppenderAttachable<IAccessEvent>, FilterAttachable<IAccessEvent> { 087 088 public final static String DEFAULT_FILENAME = "logback-access.xml"; 089 public final static String DEFAULT_CONFIG_FILE = "conf" + File.separatorChar + DEFAULT_FILENAME; 090 final static String CATALINA_BASE_KEY = "catalina.base"; 091 final static String CATALINA_HOME_KEY = "catalina.home"; 092 093 private final LifeCycleManager lifeCycleManager = new LifeCycleManager(); 094 095 private long birthTime = System.currentTimeMillis(); 096 097 LogbackLock configurationLock = new LogbackLock(); 098 099 // Attributes from ContextBase: 100 private String name; 101 StatusManager sm = new BasicStatusManager(); 102 // TODO propertyMap should be observable so that we can be notified 103 // when it changes so that a new instance of propertyMap can be 104 // serialized. For the time being, we ignore this shortcoming. 105 Map<String, String> propertyMap = new HashMap<String, String>(); 106 Map<String, Object> objectMap = new HashMap<String, Object>(); 107 private FilterAttachableImpl<IAccessEvent> fai = new FilterAttachableImpl<IAccessEvent>(); 108 109 AppenderAttachableImpl<IAccessEvent> aai = new AppenderAttachableImpl<IAccessEvent>(); 110 String filenameOption; 111 boolean quiet; 112 boolean started; 113 boolean alreadySetLogbackStatusManager = false; 114 private SequenceNumberGenerator sequenceNumberGenerator; 115 116 private ScheduledExecutorService scheduledExecutorService; 117 118 public LogbackValve() { 119 putObject(CoreConstants.EVALUATOR_MAP, new HashMap<String, EventEvaluator<?>>()); 120 } 121 122 public boolean isStarted() { 123 return started; 124 } 125 126 @Override 127 public void startInternal() throws LifecycleException { 128 scheduledExecutorService = ExecutorServiceUtil.newScheduledExecutorService(); 129 130 String filename; 131 132 if (filenameOption != null) { 133 filename = filenameOption; 134 } else { 135 addInfo("filename property not set. Assuming [" + DEFAULT_CONFIG_FILE + "]"); 136 filename = DEFAULT_CONFIG_FILE; 137 } 138 139 // String catalinaBase = OptionHelper.getSystemProperty(CATALINA_BASE_KEY); 140 // String catalinaHome = OptionHelper.getSystemProperty(CATALINA_BASE_KEY); 141 142 File configFile = searchForConfigFileTomcatProperty(filename, CATALINA_BASE_KEY); 143 if (configFile == null) { 144 configFile = searchForConfigFileTomcatProperty(filename, CATALINA_HOME_KEY); 145 } 146 147 URL resourceURL; 148 if (configFile != null) 149 resourceURL = fileToUrl(configFile); 150 else 151 resourceURL = searchAsResource(filename); 152 153 if (resourceURL != null) { 154 configureAsResource(resourceURL); 155 } else { 156 addWarn("Failed to find valid logback-access configuration file."); 157 } 158 159 if (!quiet) { 160 StatusListenerConfigHelper.addOnConsoleListenerInstance(this, new OnConsoleStatusListener()); 161 } 162 163 started = true; 164 setState(LifecycleState.STARTING); 165 } 166 167 private URL fileToUrl(File configFile) { 168 try { 169 return configFile.toURI().toURL(); 170 } catch (MalformedURLException e) { 171 throw new IllegalStateException("File to URL conversion failed", e); 172 } 173 } 174 175 private URL searchAsResource(String filename) { 176 URL result = Loader.getResource(filename, getClass().getClassLoader()); 177 if (result != null) 178 addInfo("Found [" + filename + "] as a resource."); 179 else 180 addInfo("Could NOT find [" + filename + "] as a resource."); 181 return result; 182 } 183 184 private File searchForConfigFileTomcatProperty(String filename, String propertyKey) { 185 String propertyValue = OptionHelper.getSystemProperty(propertyKey); 186 String candidatePath = propertyValue + File.separatorChar + filename; 187 if (propertyValue == null) { 188 addInfo("System property \"" + propertyKey + "\" is not set. Skipping configuration file search with ${" 189 + propertyKey + "} path prefix."); 190 return null; 191 } 192 File candidateFile = new File(candidatePath); 193 if (candidateFile.exists()) { 194 addInfo("Found configuration file [" + candidatePath + "] using property \"" + propertyKey + "\""); 195 return candidateFile; 196 } else { 197 addInfo("Could NOT find configuration file [" + candidatePath + "] using property \"" + propertyKey + "\""); 198 return null; 199 } 200 } 201 202 public void addStatus(Status status) { 203 StatusManager sm = getStatusManager(); 204 if (sm != null) { 205 sm.add(status); 206 } 207 } 208 209 public void addInfo(String msg) { 210 addStatus(new InfoStatus(msg, this)); 211 } 212 213 public void addWarn(String msg) { 214 addStatus(new WarnStatus(msg, this)); 215 } 216 217 public void addError(String msg, Throwable t) { 218 addStatus(new ErrorStatus(msg, this, t)); 219 } 220 221 private void configureAsResource(URL resourceURL) { 222 try { 223 JoranConfigurator jc = new JoranConfigurator(); 224 jc.setContext(this); 225 jc.doConfigure(resourceURL); 226 addInfo("Done configuring"); 227 } catch (JoranException e) { 228 addError("Failed to configure LogbackValve", e); 229 } 230 } 231 232 public String getFilename() { 233 return filenameOption; 234 } 235 236 public void setFilename(String filename) { 237 this.filenameOption = filename; 238 } 239 240 public boolean isQuiet() { 241 return quiet; 242 } 243 244 public void setQuiet(boolean quiet) { 245 this.quiet = quiet; 246 } 247 248 @Override 249 public void invoke(Request request, Response response) throws IOException, ServletException { 250 try { 251 if (!alreadySetLogbackStatusManager) { 252 alreadySetLogbackStatusManager = true; 253 org.apache.catalina.Context tomcatContext = request.getContext(); 254 if (tomcatContext != null) { 255 ServletContext sc = tomcatContext.getServletContext(); 256 if (sc != null) { 257 sc.setAttribute(AccessConstants.LOGBACK_STATUS_MANAGER_KEY, getStatusManager()); 258 } 259 } 260 } 261 262 getNext().invoke(request, response); 263 264 TomcatServerAdapter adapter = new TomcatServerAdapter(request, response); 265 IAccessEvent accessEvent = new AccessEvent(this, request, response, adapter); 266 267 addThreadName(accessEvent); 268 269 if (getFilterChainDecision(accessEvent) == FilterReply.DENY) { 270 return; 271 } 272 273 // TODO better exception handling 274 aai.appendLoopOnAppenders(accessEvent); 275 } finally { 276 request.removeAttribute(AccessConstants.LOGBACK_STATUS_MANAGER_KEY); 277 } 278 } 279 280 private void addThreadName(IAccessEvent accessEvent) { 281 try { 282 final String threadName = Thread.currentThread().getName(); 283 if (threadName != null) { 284 accessEvent.setThreadName(threadName); 285 } 286 } catch (Exception ignored) { 287 } 288 } 289 290 @Override 291 protected void stopInternal() throws LifecycleException { 292 started = false; 293 setState(LifecycleState.STOPPING); 294 lifeCycleManager.reset(); 295 if (scheduledExecutorService != null) { 296 ExecutorServiceUtil.shutdown(scheduledExecutorService); 297 scheduledExecutorService = null; 298 } 299 } 300 301 @Override 302 public void addAppender(Appender<IAccessEvent> newAppender) { 303 aai.addAppender(newAppender); 304 } 305 306 @Override 307 public Iterator<Appender<IAccessEvent>> iteratorForAppenders() { 308 return aai.iteratorForAppenders(); 309 } 310 311 @Override 312 public Appender<IAccessEvent> getAppender(String name) { 313 return aai.getAppender(name); 314 } 315 316 @Override 317 public boolean isAttached(Appender<IAccessEvent> appender) { 318 return aai.isAttached(appender); 319 } 320 321 @Override 322 public void detachAndStopAllAppenders() { 323 aai.detachAndStopAllAppenders(); 324 325 } 326 327 @Override 328 public boolean detachAppender(Appender<IAccessEvent> appender) { 329 return aai.detachAppender(appender); 330 } 331 332 @Override 333 public boolean detachAppender(String name) { 334 return aai.detachAppender(name); 335 } 336 337 public String getInfo() { 338 return "Logback's implementation of ValveBase"; 339 } 340 341 // Methods from ContextBase: 342 @Override 343 public StatusManager getStatusManager() { 344 return sm; 345 } 346 347 public Map<String, String> getPropertyMap() { 348 return propertyMap; 349 } 350 351 @Override 352 public void putProperty(String key, String val) { 353 this.propertyMap.put(key, val); 354 } 355 356 @Override 357 public String getProperty(String key) { 358 return (String) this.propertyMap.get(key); 359 } 360 361 @Override 362 public Map<String, String> getCopyOfPropertyMap() { 363 return new HashMap<String, String>(this.propertyMap); 364 } 365 366 @Override 367 public Object getObject(String key) { 368 return objectMap.get(key); 369 } 370 371 @Override 372 public void putObject(String key, Object value) { 373 objectMap.put(key, value); 374 } 375 376 @Override 377 public void addFilter(Filter<IAccessEvent> newFilter) { 378 fai.addFilter(newFilter); 379 } 380 381 @Override 382 public void clearAllFilters() { 383 fai.clearAllFilters(); 384 } 385 386 @Override 387 public List<Filter<IAccessEvent>> getCopyOfAttachedFiltersList() { 388 return fai.getCopyOfAttachedFiltersList(); 389 } 390 391 @Override 392 public FilterReply getFilterChainDecision(IAccessEvent event) { 393 return fai.getFilterChainDecision(event); 394 } 395 396 @Override 397 public ExecutorService getExecutorService() { 398 return getScheduledExecutorService(); 399 } 400 401 @Override 402 public String getName() { 403 return name; 404 } 405 406 @Override 407 public void setName(String name) { 408 if (this.name != null) { 409 throw new IllegalStateException("LogbackValve has been already given a name"); 410 } 411 this.name = name; 412 } 413 414 @Override 415 public long getBirthTime() { 416 return birthTime; 417 } 418 419 @Override 420 public Object getConfigurationLock() { 421 return configurationLock; 422 } 423 424 @Override 425 public void register(LifeCycle component) { 426 lifeCycleManager.register(component); 427 } 428 429 // ====== Methods from catalina Lifecycle ===== 430 431 @Override 432 public void addLifecycleListener(LifecycleListener arg0) { 433 // dummy NOP implementation 434 } 435 436 @Override 437 public LifecycleListener[] findLifecycleListeners() { 438 return new LifecycleListener[0]; 439 } 440 441 @Override 442 public void removeLifecycleListener(LifecycleListener arg0) { 443 // dummy NOP implementation 444 } 445 446 @Override 447 public String toString() { 448 StringBuilder sb = new StringBuilder(this.getClass().getName()); 449 sb.append('['); 450 sb.append(getName()); 451 sb.append(']'); 452 return sb.toString(); 453 } 454 455 @Override 456 public ScheduledExecutorService getScheduledExecutorService() { 457 return scheduledExecutorService; 458 } 459 460 @Override 461 public void addScheduledFuture(ScheduledFuture<?> scheduledFuture) { 462 throw new UnsupportedOperationException(); 463 } 464 465 public SequenceNumberGenerator getSequenceNumberGenerator() { 466 return sequenceNumberGenerator; 467 } 468 469 public void setSequenceNumberGenerator(SequenceNumberGenerator sequenceNumberGenerator) { 470 this.sequenceNumberGenerator = sequenceNumberGenerator; 471 } 472}