001/** 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved. 004 * <p> 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 * <p> 009 * or (per the licensee's choosing) 010 * <p> 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.jetty; 015 016import java.io.File; 017import java.net.URL; 018import java.util.HashMap; 019import java.util.Iterator; 020import java.util.List; 021 022import ch.qos.logback.access.joran.JoranConfigurator; 023import ch.qos.logback.access.spi.AccessEvent; 024import ch.qos.logback.access.spi.IAccessEvent; 025import ch.qos.logback.core.Appender; 026import ch.qos.logback.core.ContextBase; 027import ch.qos.logback.core.CoreConstants; 028import ch.qos.logback.core.boolex.EventEvaluator; 029import ch.qos.logback.core.filter.Filter; 030import ch.qos.logback.core.joran.spi.JoranException; 031import ch.qos.logback.core.spi.AppenderAttachable; 032import ch.qos.logback.core.spi.AppenderAttachableImpl; 033import ch.qos.logback.core.spi.FilterAttachable; 034import ch.qos.logback.core.spi.FilterAttachableImpl; 035import ch.qos.logback.core.spi.FilterReply; 036import ch.qos.logback.core.status.ErrorStatus; 037import ch.qos.logback.core.status.InfoStatus; 038import ch.qos.logback.core.util.EnvUtil; 039import ch.qos.logback.core.util.FileUtil; 040import ch.qos.logback.core.util.OptionHelper; 041import ch.qos.logback.core.util.StatusPrinter; 042import org.eclipse.jetty.server.Request; 043import org.eclipse.jetty.server.RequestLog; 044import org.eclipse.jetty.server.Response; 045import org.eclipse.jetty.util.component.LifeCycle; 046 047/** 048 * This class is logback's implementation of jetty's RequestLog interface. 049 * <p> 050 * It can be seen as logback classic's LoggerContext. Appenders can be attached 051 * directly to RequestLogImpl and RequestLogImpl uses the same StatusManager as 052 * LoggerContext does. It also provides containers for properties. 053 * 054 * </p> 055 * <h2>Supported Jetty Versions</h2> 056 * <p> 057 * This {@code RequestLogImpl} only supports Jetty 7.0.0 through Jetty 10. 058 * If you are using Jetty 11 with the new Jakarta Servlets (namespace {@code jakarta.servlet}) 059 * then you will need a more modern version of {@code logback-access}. 060 * </p> 061 * <h2>Configuring for Jetty 9.4.x through to Jetty 10.0.x</h2> 062 * <p> 063 * Jetty 9.4.x and Jetty 10.x use a modern {@code org.eclipse.jetty.server.Server.setRequestLog(RequestLog)} 064 * interface that is based on a Server level RequestLog behavior. This means all requests are logged, 065 * even bad requests, and context-less requests. 066 * </p> 067 * <p> 068 * The internals of the Jetty Request and Response objects track the state of the object at the time 069 * they are committed (the actual state during the application when an action on the network commits the 070 * request/response exchange). This prevents behaviors from 3rd party libraries 071 * that change the state of the request / response before the RequestLog gets a chance 072 * to log the details. This differs from Jetty 9.3.x and 073 * older in that those versions used a (now deprecated) {@code RequestLogHandler} and 074 * would never see bad requests, or context-less requests, 075 * and if a 3rd party library modifies the the response (for example by setting 076 * {@code response.setStatus(200)} after the response has been initiated on the network) 077 * this change in status would be logged, instead of the actual status that was sent. 078 * </p> 079 * <p> 080 * First, you must be using the proper {@code ${jetty.home}} and {@code ${jetty.base}} 081 * directory split. Configure your {@code ${jetty.base}} with at least the `resources` module 082 * enabled (so that your configuration can be found). 083 * </p> 084 * <p> 085 * Next, create a {@code ${jetty.base}/etc/logback-access.xml} file with the following 086 * content. 087 * </p> 088 * <pre> 089 * <?xml version="1.0"?> 090 * <!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd"> 091 * 092 * <Configure id="Server" class="org.eclipse.jetty.server.Server"> 093 * <Set name="requestLog"> 094 * <New id="LogbackAccess" class="ch.qos.logback.access.jetty.RequestLogImpl"> 095 * <Set name="resource">logback-access.xml</Set> 096 * </New> 097 * </Set> 098 * </Configure></pre> 099 * 100 * <p> 101 * Now you'll need a {@code ${jetty.base}/resources/logback-access.xml} configuration file. 102 * </p> 103 * 104 * <p> 105 * By default, {@code RequestLogImpl} looks for a logback configuration file called 106 * {@code etc/logback-access.xml}, in the {@code ${jetty.base}} directory, then 107 * the older {@code ${jetty.home}} directory. 108 * </p> 109 * <p> 110 * The {@code logback-access.xml} file is slightly 111 * different than the usual logback classic configuration file. Most of it is 112 * the same: {@link Appender Appenders} and {@link ch.qos.logback.core.Layout layouts} 113 * are declared the exact same way. However, 114 * loggers elements are not allowed. 115 * </p> 116 * 117 * <p> It is possible to place the logback configuration file anywhere, as long as it's path is specified. 118 * Here is another example, with an arbitrary path to the logback-access.xml file. 119 * <p/> 120 * 121 * <pre> 122 * <?xml version="1.0"?> 123 * <!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd"> 124 * 125 * <Configure id="Server" class="org.eclipse.jetty.server.Server"> 126 * <Set name="requestLog"> 127 * <New id="LogbackAccess" class="ch.qos.logback.access.jetty.RequestLogImpl"> 128 * <Set name="fileName">/arbitrary/path/to/logback-access.xml</Set> 129 * </New> 130 * </Set> 131 * </Configure> 132 * </pre> 133 * <h2>Configuring for Jetty 7.x thru to Jetty 9.3.x</h2> 134 * <p> 135 * To configure these older Jetty instances to use {@code RequestLogImpl}, 136 * the use of the {@code RequestLogHandler} is the technique available to you. 137 * Modify your {@code etc/jetty-requestlog.xml} 138 * </p> 139 * 140 * <pre> 141 * <?xml version="1.0"?> 142 * <!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd"> 143 * 144 * <Configure id="Server" class="org.eclipse.jetty.server.Server"> 145 * <Ref id="Handlers"> 146 * <Call name="addHandler"> 147 * <Arg> 148 * <New id="RequestLog" class="org.eclipse.jetty.server.handler.RequestLogHandler"> 149 * <Set name="requestLog"> 150 * <New id="RequestLogImpl" class="ch.qos.logback.access.jetty.RequestLogImpl"/> 151 * </Set> 152 * </New> 153 * </Arg> 154 * </Call> 155 * </Ref> 156 * </Configure> 157 * </pre> 158 * 159 * <p>By default, RequestLogImpl looks for a logback configuration file called 160 * logback-access.xml, in the same folder where jetty.xml is located, that is 161 * <em>etc/logback-access.xml</em>. The logback-access.xml file is slightly 162 * different from the usual logback classic configuration file. Most of it is 163 * the same: Appenders and Layouts are declared the exact same way. However, 164 * loggers elements are not allowed. 165 * </p> 166 * 167 * <p> 168 * It is possible to put the logback configuration file anywhere, as long as 169 * it's path is specified. Here is another example, with a path to the 170 * logback-access.xml file. 171 * <p/> 172 * 173 * <pre> 174 * <?xml version="1.0"?> 175 * <!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd"> 176 * 177 * <Configure id="Server" class="org.eclipse.jetty.server.Server"> 178 * <Ref id="Handlers"> 179 * <Call name="addHandler"> 180 * <Arg> 181 * <New id="RequestLog" class="org.eclipse.jetty.server.handler.RequestLogHandler"> 182 * <Set name="requestLog"> 183 * <New id="RequestLogImpl" class="ch.qos.logback.access.jetty.RequestLogImpl"> 184 * <Set name="fileName">path/to/logback-access.xml</Set> 185 * </New> 186 * </Set> 187 * </New> 188 * </Arg> 189 * </Call> 190 * </Ref> 191 * </Configure> 192 * </pre> 193 * <p> 194 * Next is a sample logback-access.xml file printing access events on the console. 195 * <p/> 196 * 197 * <pre> 198 * <configuration> 199 * <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 200 * <layout class="ch.qos.logback.access.PatternLayout"> 201 * <param name="Pattern" value="%date %server %remoteIP %clientHost %user %requestURL" /> 202 * </layout> 203 * </appender> 204 * 205 * <appender-ref ref="STDOUT" /> 206 * </configuration> 207 * </pre> 208 * <p> 209 * Here is another configuration file, using SMTPAppender: 210 * <p/> 211 * 212 * <pre> 213 * <configuration> 214 * <appender name="SMTP" class="ch.qos.logback.access.net.SMTPAppender"> 215 * <layout class="ch.qos.logback.access.PatternLayout"> 216 * <param name="pattern" value="%remoteIP [%date] %requestURL %statusCode %bytesSent" /> 217 * </layout> 218 * <param name="From" value="sender@domaine.org" /> 219 * <param name="SMTPHost" value="mail.domain.org" /> 220 * <param name="Subject" value="Last Event: %statusCode %requestURL" /> 221 * <param name="To" value="server_admin@domain.org" /> 222 * </appender> 223 * <appender-ref ref="SMTP" /> 224 * </configuration> 225 * </pre> 226 * 227 * @author Ceki Gülcü 228 * @author Sébastien Pennec 229 * @author Joakim Erdfelt 230 */ 231public class RequestLogImpl extends ContextBase implements org.eclipse.jetty.util.component.LifeCycle, RequestLog, AppenderAttachable<IAccessEvent>, FilterAttachable<IAccessEvent> { 232 233 public final static String DEFAULT_CONFIG_FILE = "etc" + File.separatorChar + "logback-access.xml"; 234 235 enum State { 236 FAILED, STOPPED, STARTING, STARTED, STOPPING 237 } 238 239 State state = State.STOPPED; 240 241 AppenderAttachableImpl<IAccessEvent> aai = new AppenderAttachableImpl<IAccessEvent>(); 242 FilterAttachableImpl<IAccessEvent> fai = new FilterAttachableImpl<IAccessEvent>(); 243 String fileName; 244 String resource; 245 246 // Jetty 9.4.x and newer is considered modern. 247 boolean modernJettyRequestLog; 248 boolean quiet = false; 249 250 public RequestLogImpl() { 251 putObject(CoreConstants.EVALUATOR_MAP, new HashMap<String, EventEvaluator<?>>()); 252 253 // plumb the depths of Jetty and the environment ... 254 if (EnvUtil.isClassAvailable(this.getClass(), "jakarta.servlet.http.HttpServlet")) { 255 throw new RuntimeException("The new jakarta.servlet classes are not supported by this " + "version of logback-access (check for a newer version of logback-access that " + "does support it)"); 256 } 257 258 // look for modern approach to RequestLog 259 modernJettyRequestLog = EnvUtil.isClassAvailable(this.getClass(), "org.eclipse.jetty.server.CustomRequestLog"); 260 } 261 262 @Override 263 public void log(Request jettyRequest, Response jettyResponse) { 264 JettyServerAdapter adapter = makeJettyServerAdapter(jettyRequest, jettyResponse); 265 IAccessEvent accessEvent = new AccessEvent(this, jettyRequest, jettyResponse, adapter); 266 if (getFilterChainDecision(accessEvent) == FilterReply.DENY) { 267 return; 268 } 269 aai.appendLoopOnAppenders(accessEvent); 270 } 271 272 private JettyServerAdapter makeJettyServerAdapter(Request jettyRequest, Response jettyResponse) { 273 if (modernJettyRequestLog) { 274 return new JettyModernServerAdapter(jettyRequest, jettyResponse); 275 } else { 276 return new JettyServerAdapter(jettyRequest, jettyResponse); 277 } 278 } 279 280 protected void addInfo(String msg) { 281 getStatusManager().add(new InfoStatus(msg, this)); 282 } 283 284 private void addError(String msg) { 285 getStatusManager().add(new ErrorStatus(msg, this)); 286 } 287 288 @Override 289 public void start() { 290 state = State.STARTING; 291 try { 292 configure(); 293 if (!isQuiet()) { 294 StatusPrinter.print(getStatusManager()); 295 } 296 state = State.STARTED; 297 } catch (Throwable t) { 298 t.printStackTrace(); 299 state = State.FAILED; 300 } 301 } 302 303 protected void configure() { 304 URL configURL = getConfigurationFileURL(); 305 if (configURL != null) { 306 runJoranOnFile(configURL); 307 } else { 308 addError("Could not find configuration file for logback-access"); 309 } 310 } 311 312 protected URL getConfigurationFileURL() { 313 if (fileName != null) { 314 addInfo("Will use configuration file [" + fileName + "]"); 315 File file = new File(fileName); 316 if (!file.exists()) return null; 317 return FileUtil.fileToURL(file); 318 } 319 if (resource != null) { 320 addInfo("Will use configuration resource [" + resource + "]"); 321 return this.getClass().getResource(resource); 322 } 323 324 String defaultConfigFile = DEFAULT_CONFIG_FILE; 325 // Always attempt ${jetty.base} first 326 String jettyBaseProperty = OptionHelper.getSystemProperty("jetty.base"); 327 if (!OptionHelper.isNullOrEmpty(jettyBaseProperty)) { 328 defaultConfigFile = jettyBaseProperty + File.separatorChar + DEFAULT_CONFIG_FILE; 329 } 330 331 File file = new File(defaultConfigFile); 332 if (!file.exists()) { 333 // Then use ${jetty.home} (not supported in Jetty 10+) 334 String jettyHomeProperty = OptionHelper.getSystemProperty("jetty.home"); 335 if (!OptionHelper.isEmpty(jettyHomeProperty)) { 336 defaultConfigFile = jettyHomeProperty + File.separatorChar + DEFAULT_CONFIG_FILE; 337 } else { 338 addInfo("Neither [jetty.base] nor [jetty.home] system properties are set."); 339 } 340 } 341 342 file = new File(defaultConfigFile); 343 addInfo("Assuming default configuration file [" + defaultConfigFile + "]"); 344 if (!file.exists()) return null; 345 return FileUtil.fileToURL(file); 346 } 347 348 private void runJoranOnFile(URL configURL) { 349 try { 350 JoranConfigurator jc = new JoranConfigurator(); 351 jc.setContext(this); 352 jc.doConfigure(configURL); 353 if (getName() == null) { 354 setName("LogbackRequestLog"); 355 } 356 } catch (JoranException e) { 357 // errors have been registered as status messages 358 } 359 } 360 361 @Override 362 public void stop() { 363 state = State.STOPPING; 364 aai.detachAndStopAllAppenders(); 365 state = State.STOPPED; 366 } 367 368 @Override 369 public boolean isRunning() { 370 return state == State.STARTED; 371 } 372 373 public void setFileName(String fileName) { 374 this.fileName = fileName; 375 } 376 377 public void setResource(String resource) { 378 this.resource = resource; 379 } 380 381 @Override 382 public boolean isStarted() { 383 return state == State.STARTED; 384 } 385 386 @Override 387 public boolean isStarting() { 388 return state == State.STARTING; 389 } 390 391 @Override 392 public boolean isStopping() { 393 return state == State.STOPPING; 394 } 395 396 public boolean isStopped() { 397 return state == State.STOPPED; 398 } 399 400 @Override 401 public boolean isFailed() { 402 return state == State.FAILED; 403 } 404 405 406 public boolean isQuiet() { 407 return quiet; 408 } 409 410 public void setQuiet(boolean quiet) { 411 this.quiet = quiet; 412 } 413 414 @Override 415 public void addAppender(Appender<IAccessEvent> newAppender) { 416 aai.addAppender(newAppender); 417 } 418 419 @Override 420 public Iterator<Appender<IAccessEvent>> iteratorForAppenders() { 421 return aai.iteratorForAppenders(); 422 } 423 424 @Override 425 public Appender<IAccessEvent> getAppender(String name) { 426 return aai.getAppender(name); 427 } 428 429 @Override 430 public boolean isAttached(Appender<IAccessEvent> appender) { 431 return aai.isAttached(appender); 432 } 433 434 @Override 435 public void detachAndStopAllAppenders() { 436 aai.detachAndStopAllAppenders(); 437 } 438 439 @Override 440 public boolean detachAppender(Appender<IAccessEvent> appender) { 441 return aai.detachAppender(appender); 442 } 443 444 @Override 445 public boolean detachAppender(String name) { 446 return aai.detachAppender(name); 447 } 448 449 @Override 450 public void addFilter(Filter<IAccessEvent> newFilter) { 451 fai.addFilter(newFilter); 452 } 453 454 @Override 455 public void clearAllFilters() { 456 fai.clearAllFilters(); 457 } 458 459 @Override 460 public List<Filter<IAccessEvent>> getCopyOfAttachedFiltersList() { 461 return fai.getCopyOfAttachedFiltersList(); 462 } 463 464 @Override 465 public FilterReply getFilterChainDecision(IAccessEvent event) { 466 return fai.getFilterChainDecision(event); 467 } 468 469 470 @Override 471 public void addLifeCycleListener(LifeCycle.Listener listener) { 472 // we'll implement this when asked 473 } 474 475 @Override 476 public void removeLifeCycleListener(LifeCycle.Listener listener) { 477 // we'll implement this when asked 478 } 479}