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.plugins.Plugin;
32  import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
33  import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
34  import org.apache.logging.log4j.core.config.plugins.PluginElement;
35  import org.apache.logging.log4j.core.config.plugins.convert.TypeConverter;
36  import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
37  import org.apache.logging.log4j.core.util.Assert;
38  import org.apache.logging.log4j.core.util.Booleans;
39  
40  /**
41   * This Appender writes logging events to a relational database using standard JDBC mechanisms. It takes a list of
42   * {@link ColumnConfig}s and/or {@link ColumnMapping}s with which it determines how to save the event data into the
43   * appropriate columns in the table. ColumnMapping is new as of Log4j 2.8 and supports
44   * {@linkplain TypeConverter type conversion} and persistence using {@link PreparedStatement#setObject(int, Object)}.
45   * A {@link ConnectionSource} plugin instance instructs the appender (and {@link JdbcDatabaseManager}) how to connect to
46   * the database. This appender can be reconfigured at run time.
47   *
48   * @see ColumnConfig
49   * @see ColumnMapping
50   * @see ConnectionSource
51   */
52  @Plugin(name = "JDBC", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
53  public final class JdbcAppender extends AbstractDatabaseAppender<JdbcDatabaseManager> {
54  
55      private final String description;
56  
57      private JdbcAppender(final String name, final Filter filter, final Layout<? extends Serializable> layout,
58              final boolean ignoreExceptions, final JdbcDatabaseManager manager) {
59          super(name, filter, layout, ignoreExceptions, manager);
60          this.description = this.getName() + "{ manager=" + this.getManager() + " }";
61      }
62  
63      @Override
64      public String toString() {
65          return this.description;
66      }
67  
68      /**
69       * Factory method for creating a JDBC appender within the plugin manager.
70       *
71       * @see Builder
72       * @deprecated use {@link #newBuilder()}
73       */
74      @Deprecated
75      public static <B extends Builder<B>> JdbcAppender createAppender(final String name, final String ignore,
76                                                                       final Filter filter,
77                                                                       final ConnectionSource connectionSource,
78                                                                       final String bufferSize, final String tableName,
79                                                                       final ColumnConfig[] columnConfigs) {
80          Assert.requireNonEmpty(name, "Name cannot be empty");
81          Objects.requireNonNull(connectionSource, "ConnectionSource cannot be null");
82          Assert.requireNonEmpty(tableName, "Table name cannot be empty");
83          Assert.requireNonEmpty(columnConfigs, "ColumnConfigs cannot be empty");
84  
85          final int bufferSizeInt = AbstractAppender.parseInt(bufferSize, 0);
86          final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
87  
88          return JdbcAppender.<B>newBuilder()
89              .setBufferSize(bufferSizeInt)
90              .setColumnConfigs(columnConfigs)
91              .setConnectionSource(connectionSource)
92              .setTableName(tableName)
93              .withName(name)
94              .withIgnoreExceptions(ignoreExceptions)
95              .withFilter(filter)
96              .build();
97      }
98  
99      @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 }