View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.appender.db.jdbc;
18  
19  import java.io.Serializable;
20  import java.sql.PreparedStatement;
21  import java.util.Arrays;
22  import java.util.Objects;
23  
24  import org.apache.logging.log4j.core.Appender;
25  import org.apache.logging.log4j.core.Core;
26  import org.apache.logging.log4j.core.Filter;
27  import org.apache.logging.log4j.core.Layout;
28  import org.apache.logging.log4j.core.appender.AbstractAppender;
29  import org.apache.logging.log4j.core.appender.db.AbstractDatabaseAppender;
30  import org.apache.logging.log4j.core.appender.db.ColumnMapping;
31  import org.apache.logging.log4j.core.config.Property;
32  import org.apache.logging.log4j.core.config.plugins.Plugin;
33  import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
34  import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
35  import org.apache.logging.log4j.core.config.plugins.PluginElement;
36  import org.apache.logging.log4j.core.config.plugins.convert.TypeConverter;
37  import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
38  import org.apache.logging.log4j.core.util.Assert;
39  import org.apache.logging.log4j.core.util.Booleans;
40  
41  /**
42   * This Appender writes logging events to a relational database using standard JDBC mechanisms. It takes a list of
43   * {@link ColumnConfig}s and/or {@link ColumnMapping}s with which it determines how to save the event data into the
44   * appropriate columns in the table. ColumnMapping is new as of Log4j 2.8 and supports
45   * {@linkplain TypeConverter type conversion} and persistence using {@link PreparedStatement#setObject(int, Object)}.
46   * A {@link ConnectionSource} plugin instance instructs the appender (and {@link JdbcDatabaseManager}) how to connect to
47   * the database. This appender can be reconfigured at run time.
48   *
49   * @see ColumnConfig
50   * @see ColumnMapping
51   * @see ConnectionSource
52   */
53  @Plugin(name = "JDBC", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
54  public final class JdbcAppender extends AbstractDatabaseAppender<JdbcDatabaseManager> {
55  
56      public static class Builder<B extends Builder<B>> extends AbstractDatabaseAppender.Builder<B>
57          implements org.apache.logging.log4j.core.util.Builder<JdbcAppender> {
58  
59          @PluginElement("ConnectionSource")
60          @Required(message = "No ConnectionSource provided")
61          private ConnectionSource connectionSource;
62  
63          @PluginBuilderAttribute
64          private boolean immediateFail;
65  
66          @PluginBuilderAttribute
67          private int bufferSize;
68  
69          @PluginBuilderAttribute
70          @Required(message = "No table name provided")
71          private String tableName;
72  
73          @PluginElement("ColumnConfigs")
74          private ColumnConfig[] columnConfigs;
75  
76          @PluginElement("ColumnMappings")
77          private ColumnMapping[] columnMappings;
78  
79          @PluginBuilderAttribute
80          private boolean truncateStrings = true;
81  
82          // TODO Consider moving up to AbstractDatabaseAppender.Builder.
83          @PluginBuilderAttribute
84          private long reconnectIntervalMillis = DEFAULT_RECONNECT_INTERVAL_MILLIS;
85  
86          @Override
87          public JdbcAppender build() {
88              if (Assert.isEmpty(columnConfigs) && Assert.isEmpty(columnMappings)) {
89                  LOGGER.error("Cannot create JdbcAppender without any columns.");
90                  return null;
91              }
92              final String managerName = "JdbcManager{name=" + getName() + ", bufferSize=" + bufferSize + ", tableName="
93                      + tableName + ", columnConfigs=" + Arrays.toString(columnConfigs) + ", columnMappings="
94                      + Arrays.toString(columnMappings) + '}';
95              final JdbcDatabaseManager manager = JdbcDatabaseManager.getManager(managerName, bufferSize, getLayout(),
96                      connectionSource, tableName, columnConfigs, columnMappings, immediateFail, reconnectIntervalMillis,
97                      truncateStrings);
98              if (manager == null) {
99                  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 }