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}