This is an automated email from the ASF dual-hosted git repository. remm pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/main by this push: new 81bb56eb7b Drop JDBCAccessLogValve 81bb56eb7b is described below commit 81bb56eb7b6d9939c50fed2d01d7004ec61af49f Author: remm <r...@apache.org> AuthorDate: Tue Dec 3 16:50:23 2024 +0100 Drop JDBCAccessLogValve Will see if it is eventually replaced by a DataSourceAccessLog valve with proper functionality and scalability. So far it seems there's no demand for it. --- .../apache/catalina/valves/JDBCAccessLogValve.java | 657 --------------------- .../catalina/valves/TestJDBCAccessLogValve.java | 143 ----- webapps/docs/changelog.xml | 6 + 3 files changed, 6 insertions(+), 800 deletions(-) diff --git a/java/org/apache/catalina/valves/JDBCAccessLogValve.java b/java/org/apache/catalina/valves/JDBCAccessLogValve.java deleted file mode 100644 index c598759874..0000000000 --- a/java/org/apache/catalina/valves/JDBCAccessLogValve.java +++ /dev/null @@ -1,657 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.catalina.valves; - - -import java.io.IOException; -import java.sql.Connection; -import java.sql.Driver; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.sql.Timestamp; -import java.util.Properties; - -import jakarta.servlet.ServletException; - -import org.apache.catalina.AccessLog; -import org.apache.catalina.LifecycleException; -import org.apache.catalina.connector.Request; -import org.apache.catalina.connector.Response; -import org.apache.tomcat.util.ExceptionUtils; - -/** - * <p> - * This Tomcat extension logs server access directly to a database, and can be used instead of the regular file-based - * access log implemented in AccessLogValve. To use, copy into the server/classes directory of the Tomcat installation - * and configure in server.xml as: - * </p> - * - * <pre> - * <Valve className="org.apache.catalina.valves.JDBCAccessLogValve" - * driverName="<i>your_jdbc_driver</i>" - * connectionURL="<i>your_jdbc_url</i>" - * pattern="combined" resolveHosts="false" - * /> - * </pre> - * <p> - * Many parameters can be configured, such as the database connection (with <code>driverName</code> and - * <code>connectionURL</code>), the table name (<code>tableName</code>) and the field names (corresponding to the - * get/set method names). The same options as AccessLogValve are supported, such as <code>resolveHosts</code> and - * <code>pattern</code> ("common" or "combined" only). - * </p> - * <p> - * When Tomcat is started, a database connection is created and used for all the log activity. When Tomcat is shutdown, - * the database connection is closed. This logger can be used at the level of the Engine context (being shared by all - * the defined hosts) or the Host context (one instance of the logger per host, possibly using different databases). - * </p> - * <p> - * The database table can be created with the following command: - * </p> - * - * <pre> - * CREATE TABLE access ( - * id INT UNSIGNED AUTO_INCREMENT NOT NULL, - * remoteHost CHAR(15) NOT NULL, - * userName CHAR(15), - * timestamp TIMESTAMP NOT NULL, - * query VARCHAR(255) NOT NULL, - * status SMALLINT UNSIGNED NOT NULL, - * bytes INT UNSIGNED NOT NULL, - * virtualHost VARCHAR(64) NOT NULL, - * method VARCHAR(8) NOT NULL, - * referer VARCHAR(128), - * userAgent VARCHAR(128), - * PRIMARY KEY (id), - * INDEX (timestamp), - * INDEX (remoteHost), - * INDEX (virtualHost), - * INDEX (query), - * INDEX (userAgent) - * ); - * </pre> - * <p> - * Set JDBCAccessLogValve attribute useLongContentLength="true" as you have more then 4GB outputs. Please, use long SQL - * datatype at access.bytes attribute. The datatype of bytes at oracle is <i>number</i> and other databases use <i>bytes - * BIGINT NOT NULL</i>. - * </p> - * <p> - * If the table is created as above, its name and the field names don't need to be defined. - * </p> - * <p> - * If the request method is "common", only these fields are used: - * <code>remoteHost, user, timeStamp, query, status, bytes</code> - * </p> - * - * @author Andre de Jesus - * @author Peter Rossbach - * @deprecated Non scalable design, and not documented. Will be removed in Tomcat 12. - */ -@Deprecated -public class JDBCAccessLogValve extends ValveBase implements AccessLog { - - - // ----------------------------------------------------------- Constructors - - - /** - * Class constructor. Initializes the fields with the default values. The defaults are: - * - * <pre> - * driverName = null; - * connectionURL = null; - * tableName = "access"; - * remoteHostField = "remoteHost"; - * userField = "userName"; - * timestampField = "timestamp"; - * virtualHostField = "virtualHost"; - * methodField = "method"; - * queryField = "query"; - * statusField = "status"; - * bytesField = "bytes"; - * refererField = "referer"; - * userAgentField = "userAgent"; - * pattern = "common"; - * resolveHosts = false; - * </pre> - */ - public JDBCAccessLogValve() { - super(true); - driverName = null; - connectionURL = null; - tableName = "access"; - remoteHostField = "remoteHost"; - userField = "userName"; - timestampField = "timestamp"; - virtualHostField = "virtualHost"; - methodField = "method"; - queryField = "query"; - statusField = "status"; - bytesField = "bytes"; - refererField = "referer"; - userAgentField = "userAgent"; - pattern = "common"; - resolveHosts = false; - conn = null; - ps = null; - currentTimeMillis = new java.util.Date().getTime(); - } - - - // ----------------------------------------------------- Instance Variables - - /** - * Use long contentLength as you have more 4 GB output. - * - * @since 6.0.15 - */ - protected boolean useLongContentLength = false; - - /** - * The connection username to use when trying to connect to the database. - */ - protected String connectionName = null; - - - /** - * The connection URL to use when trying to connect to the database. - */ - protected String connectionPassword = null; - - /** - * Instance of the JDBC Driver class we use as a connection factory. - */ - protected Driver driver = null; - - - protected String driverName; - protected String connectionURL; - protected String tableName; - protected String remoteHostField; - protected String userField; - protected String timestampField; - protected String virtualHostField; - protected String methodField; - protected String queryField; - protected String statusField; - protected String bytesField; - protected String refererField; - protected String userAgentField; - protected String pattern; - protected boolean resolveHosts; - - - protected Connection conn; - protected PreparedStatement ps; - - - protected long currentTimeMillis; - - /** - * Should this valve set request attributes for IP address, hostname, protocol and port used for the request. - * Default is <code>true</code>. - * - * @see #setRequestAttributesEnabled(boolean) - */ - protected boolean requestAttributesEnabled = true; - - - // ------------------------------------------------------------- Properties - - /** - * {@inheritDoc} Default is <code>true</code>. - */ - @Override - public void setRequestAttributesEnabled(boolean requestAttributesEnabled) { - this.requestAttributesEnabled = requestAttributesEnabled; - } - - @Override - public boolean getRequestAttributesEnabled() { - return requestAttributesEnabled; - } - - /** - * @return the username to use to connect to the database. - */ - public String getConnectionName() { - return connectionName; - } - - /** - * Set the username to use to connect to the database. - * - * @param connectionName Username - */ - public void setConnectionName(String connectionName) { - this.connectionName = connectionName; - } - - /** - * Sets the database driver name. - * - * @param driverName The complete name of the database driver class. - */ - public void setDriverName(String driverName) { - this.driverName = driverName; - } - - /** - * @return the password to use to connect to the database. - */ - public String getConnectionPassword() { - return connectionPassword; - } - - /** - * Set the password to use to connect to the database. - * - * @param connectionPassword User password - */ - public void setConnectionPassword(String connectionPassword) { - this.connectionPassword = connectionPassword; - } - - /** - * Sets the JDBC URL for the database where the log is stored. - * - * @param connectionURL The JDBC URL of the database. - */ - public void setConnectionURL(String connectionURL) { - this.connectionURL = connectionURL; - } - - - /** - * Sets the name of the table where the logs are stored. - * - * @param tableName The name of the table. - */ - public void setTableName(String tableName) { - this.tableName = tableName; - } - - - /** - * Sets the name of the field containing the remote host. - * - * @param remoteHostField The name of the remote host field. - */ - public void setRemoteHostField(String remoteHostField) { - this.remoteHostField = remoteHostField; - } - - - /** - * Sets the name of the field containing the remote user name. - * - * @param userField The name of the remote user field. - */ - public void setUserField(String userField) { - this.userField = userField; - } - - - /** - * Sets the name of the field containing the server-determined timestamp. - * - * @param timestampField The name of the server-determined timestamp field. - */ - public void setTimestampField(String timestampField) { - this.timestampField = timestampField; - } - - - /** - * Sets the name of the field containing the virtual host information (this is in fact the server name). - * - * @param virtualHostField The name of the virtual host field. - */ - public void setVirtualHostField(String virtualHostField) { - this.virtualHostField = virtualHostField; - } - - - /** - * Sets the name of the field containing the HTTP request method. - * - * @param methodField The name of the HTTP request method field. - */ - public void setMethodField(String methodField) { - this.methodField = methodField; - } - - - /** - * Sets the name of the field containing the URL part of the HTTP query. - * - * @param queryField The name of the field containing the URL part of the HTTP query. - */ - public void setQueryField(String queryField) { - this.queryField = queryField; - } - - - /** - * Sets the name of the field containing the HTTP response status code. - * - * @param statusField The name of the HTTP response status code field. - */ - public void setStatusField(String statusField) { - this.statusField = statusField; - } - - - /** - * Sets the name of the field containing the number of bytes returned. - * - * @param bytesField The name of the returned bytes field. - */ - public void setBytesField(String bytesField) { - this.bytesField = bytesField; - } - - - /** - * Sets the name of the field containing the referer. - * - * @param refererField The referer field name. - */ - public void setRefererField(String refererField) { - this.refererField = refererField; - } - - - /** - * Sets the name of the field containing the user agent. - * - * @param userAgentField The name of the user agent field. - */ - public void setUserAgentField(String userAgentField) { - this.userAgentField = userAgentField; - } - - - /** - * Sets the logging pattern. The patterns supported correspond to the file-based "common" and "combined". These are - * translated into the use of tables containing either set of fields. - * <P> - * <I>TO DO: more flexible field choices.</I> - * </P> - * - * @param pattern The name of the logging pattern. - */ - public void setPattern(String pattern) { - this.pattern = pattern; - } - - - /** - * Determines whether IP host name resolution is done. - * - * @param resolveHosts "true" or "false", if host IP resolution is desired or not. - */ - public void setResolveHosts(String resolveHosts) { - this.resolveHosts = Boolean.parseBoolean(resolveHosts); - } - - /** - * @return <code>true</code> if content length should be considered a long rather than an int, defaults to - * <code>false</code> - */ - public boolean getUseLongContentLength() { - return this.useLongContentLength; - } - - /** - * @param useLongContentLength the useLongContentLength to set - */ - public void setUseLongContentLength(boolean useLongContentLength) { - this.useLongContentLength = useLongContentLength; - } - - // --------------------------------------------------------- Public Methods - - - @Override - public void invoke(Request request, Response response) throws IOException, ServletException { - getNext().invoke(request, response); - } - - - @Override - public void log(Request request, Response response, long time) { - if (!getState().isAvailable()) { - return; - } - - final String EMPTY = ""; - - String remoteHost; - if (resolveHosts) { - if (requestAttributesEnabled) { - Object host = request.getAttribute(REMOTE_HOST_ATTRIBUTE); - if (host == null) { - remoteHost = request.getRemoteHost(); - } else { - remoteHost = (String) host; - } - } else { - remoteHost = request.getRemoteHost(); - } - } else { - if (requestAttributesEnabled) { - Object addr = request.getAttribute(REMOTE_ADDR_ATTRIBUTE); - if (addr == null) { - remoteHost = request.getRemoteAddr(); - } else { - remoteHost = (String) addr; - } - } else { - remoteHost = request.getRemoteAddr(); - } - } - String user = request.getRemoteUser(); - String query = request.getRequestURI(); - - long bytes = response.getBytesWritten(true); - if (bytes < 0) { - bytes = 0; - } - int status = response.getStatus(); - String virtualHost = EMPTY; - String method = EMPTY; - String referer = EMPTY; - String userAgent = EMPTY; - String logPattern = pattern; - if (logPattern.equals("combined")) { - virtualHost = request.getServerName(); - method = request.getMethod(); - referer = request.getHeader("referer"); - userAgent = request.getHeader("user-agent"); - } - synchronized (this) { - int numberOfTries = 2; - while (numberOfTries > 0) { - try { - open(); - - ps.setString(1, remoteHost); - ps.setString(2, user); - ps.setTimestamp(3, new Timestamp(getCurrentTimeMillis())); - ps.setString(4, query); - ps.setInt(5, status); - - if (useLongContentLength) { - ps.setLong(6, bytes); - } else { - if (bytes > Integer.MAX_VALUE) { - bytes = -1; - } - ps.setInt(6, (int) bytes); - } - if (logPattern.equals("combined")) { - ps.setString(7, virtualHost); - ps.setString(8, method); - ps.setString(9, referer); - ps.setString(10, userAgent); - } - ps.executeUpdate(); - return; - } catch (SQLException e) { - // Log the problem for posterity - container.getLogger().error(sm.getString("jdbcAccessLogValve.exception"), e); - - // Close the connection so that it gets reopened next time - if (conn != null) { - close(); - } - } - numberOfTries--; - } - } - - } - - - /** - * Open (if necessary) and return a database connection for use by this AccessLogValve. - * - * @exception SQLException if a database error occurs - */ - protected void open() throws SQLException { - - // Do nothing if there is a database connection already open - if (conn != null) { - return; - } - - // Instantiate our database driver if necessary - if (driver == null) { - try { - Class<?> clazz = Class.forName(driverName); - driver = (Driver) clazz.getConstructor().newInstance(); - } catch (Throwable e) { - ExceptionUtils.handleThrowable(e); - throw new SQLException(e.getMessage(), e); - } - } - - // Open a new connection - Properties props = new Properties(); - if (connectionName != null) { - props.put("user", connectionName); - } - if (connectionPassword != null) { - props.put("password", connectionPassword); - } - conn = driver.connect(connectionURL, props); - conn.setAutoCommit(true); - prepare(); - String logPattern = pattern; - if (logPattern.equals("common")) { - ps = conn.prepareStatement( - "INSERT INTO " + tableName + " (" + remoteHostField + ", " + userField + ", " + timestampField + - ", " + queryField + ", " + statusField + ", " + bytesField + ") VALUES(?, ?, ?, ?, ?, ?)"); - } else if (logPattern.equals("combined")) { - ps = conn.prepareStatement("INSERT INTO " + tableName + " (" + remoteHostField + ", " + userField + ", " + - timestampField + ", " + queryField + ", " + statusField + ", " + bytesField + ", " + - virtualHostField + ", " + methodField + ", " + refererField + ", " + userAgentField + - ") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); - } - } - - - /** - * Prepare tables for processing. Used by subclasses for testing. - * @throws SQLException if an exception occurs - */ - protected void prepare() throws SQLException { - } - - - /** - * Close the specified database connection. - */ - protected void close() { - - // Do nothing if the database connection is already closed - if (conn == null) { - return; - } - - // Close our prepared statements (if any) - try { - ps.close(); - } catch (Throwable f) { - ExceptionUtils.handleThrowable(f); - } - this.ps = null; - - - // Close this database connection, and log any errors - try { - conn.close(); - } catch (SQLException e) { - container.getLogger().error(sm.getString("jdbcAccessLogValve.close"), e); // Just log it here - } finally { - this.conn = null; - } - - } - - - /** - * Start this component and implement the requirements of - * {@link org.apache.catalina.util.LifecycleBase#startInternal()}. - * - * @exception LifecycleException if this component detects a fatal error that prevents this component from being - * used - */ - @Override - protected void startInternal() throws LifecycleException { - try { - open(); - } catch (SQLException e) { - throw new LifecycleException(e); - } - super.startInternal(); - } - - - /** - * Stop this component and implement the requirements of - * {@link org.apache.catalina.util.LifecycleBase#stopInternal()}. - * - * @exception LifecycleException if this component detects a fatal error that prevents this component from being - * used - */ - @Override - protected void stopInternal() throws LifecycleException { - super.stopInternal(); - close(); - } - - - public long getCurrentTimeMillis() { - long systime = System.currentTimeMillis(); - if ((systime - currentTimeMillis) > 1000) { - currentTimeMillis = new java.util.Date(systime).getTime(); - } - return currentTimeMillis; - } - -} diff --git a/test/org/apache/catalina/valves/TestJDBCAccessLogValve.java b/test/org/apache/catalina/valves/TestJDBCAccessLogValve.java deleted file mode 100644 index dc2597316f..0000000000 --- a/test/org/apache/catalina/valves/TestJDBCAccessLogValve.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.catalina.valves; - - -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import jakarta.servlet.http.HttpServletResponse; - -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameter; - -import org.apache.catalina.Context; -import org.apache.catalina.startup.Tomcat; -import org.apache.catalina.startup.TomcatBaseTest; -import org.apache.tomcat.util.buf.ByteChunk; - -@RunWith(Parameterized.class) -public class TestJDBCAccessLogValve extends TomcatBaseTest { - - public static final String SCHEMA = - "CREATE TABLE access (\n" + - " id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY(Start with 1, Increment by 1),\n" + - " remoteHost CHAR(15) NOT NULL,\n" + - " userName CHAR(15),\n" + - " timestamp TIMESTAMP NOT NULL,\n" + - " query VARCHAR(255),\n" + - " status SMALLINT NOT NULL,\n" + - " bytes INT NOT NULL,\n" + - " virtualHost VARCHAR(64),\n" + - " method VARCHAR(8),\n" + - " referer VARCHAR(128),\n" + - " userAgent VARCHAR(128)\n" + - ")"; - - @Parameterized.Parameters(name = "{index}: logPattern[{0}]") - public static Collection<Object[]> parameters() { - List<Object[]> parameterSets = new ArrayList<>(); - - parameterSets.add(new Object[] {"common"}); - parameterSets.add(new Object[] {"combined"}); - - return parameterSets; - } - - @Parameter(0) - public String logPattern; - - @Test - public void testValve() throws Exception { - - Tomcat tomcat = getTomcatInstance(); - // No file system docBase required - getTomcatInstanceTestWebapp(false, false); - Context ctx = (Context) tomcat.getHost().findChildren()[0]; - - CustomJDBCAccessLogValve accessLogValve = new CustomJDBCAccessLogValve(); - accessLogValve.setDriverName("org.apache.derby.jdbc.EmbeddedDriver"); - accessLogValve.setConnectionURL("jdbc:derby:" + getTemporaryDirectory().getAbsolutePath() - + "/accesslog-" + logPattern + ";create=true"); - accessLogValve.setPattern(logPattern); - ctx.getParent().getPipeline().addValve(accessLogValve); - - tomcat.start(); - - ByteChunk result = new ByteChunk(); - int rc = getUrl("http://localhost:" + getPort() + "/test/index.html", result, null); - Assert.assertEquals(HttpServletResponse.SC_OK, rc); - result.recycle(); - - rc = getUrl("http://localhost:" + getPort() + "/test/404.html?foo=bar", result, null); - Assert.assertEquals(HttpServletResponse.SC_OK, rc); - - accessLogValve.verify(); - - tomcat.stop(); - - } - - protected class CustomJDBCAccessLogValve extends JDBCAccessLogValve { - @Override - protected void prepare() throws SQLException { - try (Statement statement = conn.createStatement()) { - statement.execute(SCHEMA); - } - } - - public void verify() throws Exception { - /* - * The access log is written after the response. The client may have processed the response faster than the - * server was able to write the access log. Give the server up to 5 seconds to write the access log. - */ - int rowCount = 0; - int timerCount = 0; - while (timerCount < 500) { - try (Statement statement = conn.createStatement()) { - statement.execute("SELECT COUNT(*) FROM access"); - ResultSet rs = statement.getResultSet(); - Assert.assertTrue(rs.next()); - rowCount = rs.getInt(1); - if (rowCount > 1) { - break; - } - Thread.sleep(10); - timerCount++; - } - } - try (Statement statement = conn.createStatement()) { - statement.execute("SELECT * FROM access"); - ResultSet rs = statement.getResultSet(); - Assert.assertTrue(rs.next()); - Assert.assertEquals(HttpServletResponse.SC_OK, rs.getInt("status")); - Assert.assertEquals("/test/index.html", rs.getString("query")); - Assert.assertTrue(rs.getLong("bytes") == 934); - Assert.assertTrue(rs.next()); - Assert.assertEquals(HttpServletResponse.SC_OK, rs.getInt("status")); - Assert.assertEquals("/test/404.html", rs.getString("query")); - } - } - } -} diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index c768d9d1a5..ff62723297 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -122,6 +122,12 @@ Add support for the new Servlet API method <code>HttpServletResponse.sendEarlyHints()</code>. (markt) </add> + <update> + Remove <code>JDBCAccessLogValve</code> which provides limited + functionality compared to what is expected from an + <code>AccessLog</code> and was not implemented in a scalable way. + (remm) + </update> <!-- Entries for backport and removal before 12.0.0-M1 below this line --> <fix> Add special handling for the <code>protocols</code> attribute of --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org