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.plugins.Plugin; 032import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; 033import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; 034import org.apache.logging.log4j.core.config.plugins.PluginElement; 035import org.apache.logging.log4j.core.config.plugins.convert.TypeConverter; 036import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; 037import org.apache.logging.log4j.core.util.Assert; 038import org.apache.logging.log4j.core.util.Booleans; 039 040/** 041 * This Appender writes logging events to a relational database using standard JDBC mechanisms. It takes a list of 042 * {@link ColumnConfig}s and/or {@link ColumnMapping}s with which it determines how to save the event data into the 043 * appropriate columns in the table. ColumnMapping is new as of Log4j 2.8 and supports 044 * {@linkplain TypeConverter type conversion} and persistence using {@link PreparedStatement#setObject(int, Object)}. 045 * A {@link ConnectionSource} plugin instance instructs the appender (and {@link JdbcDatabaseManager}) how to connect to 046 * the database. This appender can be reconfigured at run time. 047 * 048 * @see ColumnConfig 049 * @see ColumnMapping 050 * @see ConnectionSource 051 */ 052@Plugin(name = "JDBC", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) 053public final class JdbcAppender extends AbstractDatabaseAppender<JdbcDatabaseManager> { 054 055 private final String description; 056 057 private JdbcAppender(final String name, final Filter filter, final Layout<? extends Serializable> layout, 058 final boolean ignoreExceptions, final JdbcDatabaseManager manager) { 059 super(name, filter, layout, ignoreExceptions, manager); 060 this.description = this.getName() + "{ manager=" + this.getManager() + " }"; 061 } 062 063 @Override 064 public String toString() { 065 return this.description; 066 } 067 068 /** 069 * Factory method for creating a JDBC appender within the plugin manager. 070 * 071 * @see Builder 072 * @deprecated use {@link #newBuilder()} 073 */ 074 @Deprecated 075 public static <B extends Builder<B>> JdbcAppender createAppender(final String name, final String ignore, 076 final Filter filter, 077 final ConnectionSource connectionSource, 078 final String bufferSize, final String tableName, 079 final ColumnConfig[] columnConfigs) { 080 Assert.requireNonEmpty(name, "Name cannot be empty"); 081 Objects.requireNonNull(connectionSource, "ConnectionSource cannot be null"); 082 Assert.requireNonEmpty(tableName, "Table name cannot be empty"); 083 Assert.requireNonEmpty(columnConfigs, "ColumnConfigs cannot be empty"); 084 085 final int bufferSizeInt = AbstractAppender.parseInt(bufferSize, 0); 086 final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true); 087 088 return JdbcAppender.<B>newBuilder() 089 .setBufferSize(bufferSizeInt) 090 .setColumnConfigs(columnConfigs) 091 .setConnectionSource(connectionSource) 092 .setTableName(tableName) 093 .withName(name) 094 .withIgnoreExceptions(ignoreExceptions) 095 .withFilter(filter) 096 .build(); 097 } 098 099 @PluginBuilderFactory 100 public static <B extends Builder<B>> B newBuilder() { 101 return new Builder<B>().asBuilder(); 102 } 103 104 public static class Builder<B extends Builder<B>> extends AbstractAppender.Builder<B> 105 implements org.apache.logging.log4j.core.util.Builder<JdbcAppender> { 106 107 @PluginElement("ConnectionSource") 108 @Required(message = "No ConnectionSource provided") 109 private ConnectionSource connectionSource; 110 111 @PluginBuilderAttribute 112 private int bufferSize; 113 114 @PluginBuilderAttribute 115 @Required(message = "No table name provided") 116 private String tableName; 117 118 @PluginElement("ColumnConfigs") 119 private ColumnConfig[] columnConfigs; 120 121 @PluginElement("ColumnMappings") 122 private ColumnMapping[] columnMappings; 123 124 /** 125 * The connections source from which database connections should be retrieved. 126 * 127 * @return this 128 */ 129 public B setConnectionSource(final ConnectionSource connectionSource) { 130 this.connectionSource = connectionSource; 131 return asBuilder(); 132 } 133 134 /** 135 * If an integer greater than 0, this causes the appender to buffer log events and flush whenever the buffer 136 * reaches this size. 137 * 138 * @return this 139 */ 140 public B setBufferSize(final int bufferSize) { 141 this.bufferSize = bufferSize; 142 return asBuilder(); 143 } 144 145 /** 146 * The name of the database table to insert log events into. 147 * 148 * @return this 149 */ 150 public B setTableName(final String tableName) { 151 this.tableName = tableName; 152 return asBuilder(); 153 } 154 155 /** 156 * Information about the columns that log event data should be inserted into and how to insert that data. 157 * 158 * @return this 159 */ 160 public B setColumnConfigs(final ColumnConfig... columnConfigs) { 161 this.columnConfigs = columnConfigs; 162 return asBuilder(); 163 } 164 165 public B setColumnMappings(final ColumnMapping... columnMappings) { 166 this.columnMappings = columnMappings; 167 return asBuilder(); 168 } 169 170 @Override 171 public JdbcAppender build() { 172 if (Assert.isEmpty(columnConfigs) && Assert.isEmpty(columnMappings)) { 173 LOGGER.error("Cannot create JdbcAppender without any columns."); 174 return null; 175 } 176 final String managerName = "JdbcManager{name=" + getName() + ", bufferSize=" + bufferSize + ", tableName=" 177 + tableName + ", columnConfigs=" + Arrays.toString(columnConfigs) + ", columnMappings=" 178 + Arrays.toString(columnMappings) + '}'; 179 final JdbcDatabaseManager manager = JdbcDatabaseManager.getManager(managerName, bufferSize, getLayout(), 180 connectionSource, tableName, columnConfigs, columnMappings); 181 if (manager == null) { 182 return null; 183 } 184 return new JdbcAppender(getName(), getFilter(), getLayout(), isIgnoreExceptions(), manager); 185 } 186 187 } 188}