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