001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017package org.apache.logging.log4j.core.appender.db.jdbc; 018 019import java.io.Serializable; 020import java.sql.PreparedStatement; 021import java.util.Arrays; 022import java.util.Objects; 023 024import org.apache.logging.log4j.core.Appender; 025import org.apache.logging.log4j.core.Core; 026import org.apache.logging.log4j.core.Filter; 027import org.apache.logging.log4j.core.Layout; 028import org.apache.logging.log4j.core.appender.AbstractAppender; 029import org.apache.logging.log4j.core.appender.db.AbstractDatabaseAppender; 030import org.apache.logging.log4j.core.appender.db.ColumnMapping; 031import org.apache.logging.log4j.core.config.Property; 032import org.apache.logging.log4j.core.config.plugins.Plugin; 033import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; 034import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; 035import org.apache.logging.log4j.core.config.plugins.PluginElement; 036import org.apache.logging.log4j.core.config.plugins.convert.TypeConverter; 037import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; 038import org.apache.logging.log4j.core.util.Assert; 039import org.apache.logging.log4j.core.util.Booleans; 040 041/** 042 * This Appender writes logging events to a relational database using standard JDBC mechanisms. It takes a list of 043 * {@link ColumnConfig}s and/or {@link ColumnMapping}s with which it determines how to save the event data into the 044 * appropriate columns in the table. ColumnMapping is new as of Log4j 2.8 and supports 045 * {@linkplain TypeConverter type conversion} and persistence using {@link PreparedStatement#setObject(int, Object)}. 046 * A {@link ConnectionSource} plugin instance instructs the appender (and {@link JdbcDatabaseManager}) how to connect to 047 * the database. This appender can be reconfigured at run time. 048 * 049 * @see ColumnConfig 050 * @see ColumnMapping 051 * @see ConnectionSource 052 */ 053@Plugin(name = "JDBC", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) 054public final class JdbcAppender extends AbstractDatabaseAppender<JdbcDatabaseManager> { 055 056 public static class Builder<B extends Builder<B>> extends AbstractDatabaseAppender.Builder<B> 057 implements org.apache.logging.log4j.core.util.Builder<JdbcAppender> { 058 059 @PluginElement("ConnectionSource") 060 @Required(message = "No ConnectionSource provided") 061 private ConnectionSource connectionSource; 062 063 @PluginBuilderAttribute 064 private boolean immediateFail; 065 066 @PluginBuilderAttribute 067 private int bufferSize; 068 069 @PluginBuilderAttribute 070 @Required(message = "No table name provided") 071 private String tableName; 072 073 @PluginElement("ColumnConfigs") 074 private ColumnConfig[] columnConfigs; 075 076 @PluginElement("ColumnMappings") 077 private ColumnMapping[] columnMappings; 078 079 @PluginBuilderAttribute 080 private boolean truncateStrings = true; 081 082 // TODO Consider moving up to AbstractDatabaseAppender.Builder. 083 @PluginBuilderAttribute 084 private long reconnectIntervalMillis = DEFAULT_RECONNECT_INTERVAL_MILLIS; 085 086 @Override 087 public JdbcAppender build() { 088 if (Assert.isEmpty(columnConfigs) && Assert.isEmpty(columnMappings)) { 089 LOGGER.error("Cannot create JdbcAppender without any columns."); 090 return null; 091 } 092 final String managerName = "JdbcManager{name=" + getName() + ", bufferSize=" + bufferSize + ", tableName=" 093 + tableName + ", columnConfigs=" + Arrays.toString(columnConfigs) + ", columnMappings=" 094 + Arrays.toString(columnMappings) + '}'; 095 final JdbcDatabaseManager manager = JdbcDatabaseManager.getManager(managerName, bufferSize, getLayout(), 096 connectionSource, tableName, columnConfigs, columnMappings, immediateFail, reconnectIntervalMillis, 097 truncateStrings); 098 if (manager == null) { 099 return null; 100 } 101 return new JdbcAppender(getName(), getFilter(), getLayout(), isIgnoreExceptions(), getPropertyArray(), 102 manager); 103 } 104 105 public long getReconnectIntervalMillis() { 106 return reconnectIntervalMillis; 107 } 108 109 public boolean isImmediateFail() { 110 return immediateFail; 111 } 112 113 /** 114 * If an integer greater than 0, this causes the appender to buffer log events and flush whenever the buffer 115 * reaches this size. 116 * 117 * @param bufferSize buffer size. 118 * 119 * @return this 120 */ 121 public B setBufferSize(final int bufferSize) { 122 this.bufferSize = bufferSize; 123 return asBuilder(); 124 } 125 126 /** 127 * Information about the columns that log event data should be inserted into and how to insert that data. 128 * 129 * @param columnConfigs Column configurations. 130 * 131 * @return this 132 */ 133 public B setColumnConfigs(final ColumnConfig... columnConfigs) { 134 this.columnConfigs = columnConfigs; 135 return asBuilder(); 136 } 137 138 public B setColumnMappings(final ColumnMapping... columnMappings) { 139 this.columnMappings = columnMappings; 140 return asBuilder(); 141 } 142 143 /** 144 * The connections source from which database connections should be retrieved. 145 * 146 * @param connectionSource The connections source. 147 * 148 * @return this 149 */ 150 public B setConnectionSource(final ConnectionSource connectionSource) { 151 this.connectionSource = connectionSource; 152 return asBuilder(); 153 } 154 155 public void setImmediateFail(final boolean immediateFail) { 156 this.immediateFail = immediateFail; 157 } 158 159 public void setReconnectIntervalMillis(final long reconnectIntervalMillis) { 160 this.reconnectIntervalMillis = reconnectIntervalMillis; 161 } 162 163 /** 164 * The name of the database table to insert log events into. 165 * 166 * @param tableName The database table name. 167 * 168 * @return this 169 */ 170 public B setTableName(final String tableName) { 171 this.tableName = tableName; 172 return asBuilder(); 173 } 174 175 public B setTruncateStrings(final boolean truncateStrings) { 176 this.truncateStrings = truncateStrings; 177 return asBuilder(); 178 } 179 180 } 181 182 /** 183 * Factory method for creating a JDBC appender within the plugin manager. 184 * 185 * @see Builder 186 * @deprecated use {@link #newBuilder()} 187 */ 188 @Deprecated 189 public static <B extends Builder<B>> JdbcAppender createAppender(final String name, final String ignore, 190 final Filter filter, 191 final ConnectionSource connectionSource, 192 final String bufferSize, final String tableName, 193 final ColumnConfig[] columnConfigs) { 194 Assert.requireNonEmpty(name, "Name cannot be empty"); 195 Objects.requireNonNull(connectionSource, "ConnectionSource cannot be null"); 196 Assert.requireNonEmpty(tableName, "Table name cannot be empty"); 197 Assert.requireNonEmpty(columnConfigs, "ColumnConfigs cannot be empty"); 198 199 final int bufferSizeInt = AbstractAppender.parseInt(bufferSize, 0); 200 final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true); 201 202 return JdbcAppender.<B>newBuilder() 203 .setBufferSize(bufferSizeInt) 204 .setColumnConfigs(columnConfigs) 205 .setConnectionSource(connectionSource) 206 .setTableName(tableName).setName(name).setIgnoreExceptions(ignoreExceptions).setFilter(filter) 207 .build(); 208 } 209 210 @PluginBuilderFactory 211 public static <B extends Builder<B>> B newBuilder() { 212 return new Builder<B>().asBuilder(); 213 } 214 215 private final String description; 216 217 private JdbcAppender(final String name, final Filter filter, final Layout<? extends Serializable> layout, 218 final boolean ignoreExceptions, final Property[] properties, final JdbcDatabaseManager manager) { 219 super(name, filter, layout, ignoreExceptions, properties, manager); 220 this.description = this.getName() + "{ manager=" + this.getManager() + " }"; 221 } 222 223 @Override 224 public String toString() { 225 return this.description; 226 } 227}