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