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.nio.charset.Charset; 021import java.sql.PreparedStatement; 022import java.util.Arrays; 023import java.util.Objects; 024 025import org.apache.logging.log4j.core.Appender; 026import org.apache.logging.log4j.core.Core; 027import org.apache.logging.log4j.core.Filter; 028import org.apache.logging.log4j.core.Layout; 029import org.apache.logging.log4j.core.appender.AbstractAppender; 030import org.apache.logging.log4j.core.appender.db.AbstractDatabaseAppender; 031import org.apache.logging.log4j.core.appender.db.ColumnMapping; 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 private final String description; 057 058 private JdbcAppender(final String name, final Filter filter, final boolean ignoreExceptions, 059 final JdbcDatabaseManager manager) { 060 super(name, filter, ignoreExceptions, manager); 061 this.description = this.getName() + "{ manager=" + this.getManager() + " }"; 062 } 063 064 @Override 065 public String toString() { 066 return this.description; 067 } 068 069 /** 070 * Factory method for creating a JDBC appender within the plugin manager. 071 * 072 * @see Builder 073 * @deprecated use {@link #newBuilder()} 074 */ 075 @Deprecated 076 public static <B extends Builder<B>> JdbcAppender createAppender(final String name, final String ignore, 077 final Filter filter, 078 final ConnectionSource connectionSource, 079 final String bufferSize, final String tableName, 080 final ColumnConfig[] columnConfigs) { 081 Assert.requireNonEmpty(name, "Name cannot be empty"); 082 Objects.requireNonNull(connectionSource, "ConnectionSource cannot be null"); 083 Assert.requireNonEmpty(tableName, "Table name cannot be empty"); 084 Assert.requireNonEmpty(columnConfigs, "ColumnConfigs cannot be empty"); 085 086 final int bufferSizeInt = AbstractAppender.parseInt(bufferSize, 0); 087 final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true); 088 089 return JdbcAppender.<B>newBuilder() 090 .setBufferSize(bufferSizeInt) 091 .setColumnConfigs(columnConfigs) 092 .setConnectionSource(connectionSource) 093 .setTableName(tableName) 094 .withName(name) 095 .withIgnoreExceptions(ignoreExceptions) 096 .withFilter(filter) 097 .build(); 098 } 099 100 @PluginBuilderFactory 101 public static <B extends Builder<B>> B newBuilder() { 102 return new Builder<B>().asBuilder(); 103 } 104 105 public static class Builder<B extends Builder<B>> extends AbstractAppender.Builder<B> 106 implements org.apache.logging.log4j.core.util.Builder<JdbcAppender> { 107 108 @PluginElement("ConnectionSource") 109 @Required(message = "No ConnectionSource provided") 110 private ConnectionSource connectionSource; 111 112 @PluginBuilderAttribute 113 private int bufferSize; 114 115 @PluginBuilderAttribute 116 @Required(message = "No table name provided") 117 private String tableName; 118 119 @PluginElement("ColumnConfigs") 120 private ColumnConfig[] columnConfigs; 121 122 @PluginElement("ColumnMappings") 123 private ColumnMapping[] columnMappings; 124 125 /** 126 * The connections source from which database connections should be retrieved. 127 */ 128 public B setConnectionSource(final ConnectionSource connectionSource) { 129 this.connectionSource = connectionSource; 130 return asBuilder(); 131 } 132 133 /** 134 * If an integer greater than 0, this causes the appender to buffer log events and flush whenever the buffer 135 * reaches this size. 136 */ 137 public B setBufferSize(final int bufferSize) { 138 this.bufferSize = bufferSize; 139 return asBuilder(); 140 } 141 142 /** 143 * The name of the database table to insert log events into. 144 */ 145 public B setTableName(final String tableName) { 146 this.tableName = tableName; 147 return asBuilder(); 148 } 149 150 /** 151 * Information about the columns that log event data should be inserted into and how to insert that data. 152 */ 153 public B setColumnConfigs(final ColumnConfig... columnConfigs) { 154 this.columnConfigs = columnConfigs; 155 return asBuilder(); 156 } 157 158 public B setColumnMappings(final ColumnMapping... columnMappings) { 159 this.columnMappings = columnMappings; 160 return asBuilder(); 161 } 162 163 @Override 164 public JdbcAppender build() { 165 if (Assert.isEmpty(columnConfigs) && Assert.isEmpty(columnMappings)) { 166 LOGGER.error("Cannot create JdbcAppender without any columns configured."); 167 return null; 168 } 169 final String managerName = "JdbcManager{name=" + getName() + ", bufferSize=" + bufferSize + ", tableName=" + 170 tableName + ", columnConfigs=" + Arrays.toString(columnConfigs) + ", columnMappings=" + 171 Arrays.toString(columnMappings) + '}'; 172 final JdbcDatabaseManager manager = JdbcDatabaseManager.getManager(managerName, bufferSize, 173 connectionSource, tableName, columnConfigs, columnMappings); 174 if (manager == null) { 175 return null; 176 } 177 return new JdbcAppender(getName(), getFilter(), isIgnoreExceptions(), manager); 178 } 179 180 @Override 181 @Deprecated 182 public Layout<? extends Serializable> getLayout() { 183 throw new UnsupportedOperationException(); 184 } 185 186 @Override 187 @Deprecated 188 public B withLayout(final Layout<? extends Serializable> layout) { 189 throw new UnsupportedOperationException(); 190 } 191 192 @Override 193 @Deprecated 194 public Layout<? extends Serializable> getOrCreateLayout() { 195 throw new UnsupportedOperationException(); 196 } 197 198 @Override 199 @Deprecated 200 public Layout<? extends Serializable> getOrCreateLayout(final Charset charset) { 201 throw new UnsupportedOperationException(); 202 } 203 } 204}