    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     */
    017    package org.apache.logging.log4j.core.appender.db;
    018
    019    import java.util.concurrent.locks.Lock;
    020    import java.util.concurrent.locks.ReadWriteLock;
    021    import java.util.concurrent.locks.ReentrantReadWriteLock;
    022
    023    import org.apache.logging.log4j.LoggingException;
    024    import org.apache.logging.log4j.core.Filter;
    025    import org.apache.logging.log4j.core.Layout;
    026    import org.apache.logging.log4j.core.LogEvent;
    027    import org.apache.logging.log4j.core.appender.AbstractAppender;
    028    import org.apache.logging.log4j.core.appender.AppenderLoggingException;
    029
    030    /**
    031     * An abstract Appender for writing events to a database of some type, be it relational or NoSQL. All database appenders
    032     * should inherit from this base appender. Three implementations are currently provided:
    033     * {@link org.apache.logging.log4j.core.appender.db.jdbc JDBC}, {@link org.apache.logging.log4j.core.appender.db.jpa
    034     * JPA}, and <a href="/log4j/2.x/log4j-nosql/apidocs/">NoSQL</a>.
    035     *
    036     * @param <T> Specifies which type of {@link AbstractDatabaseManager} this Appender requires.
    037     */
    038    public abstract class AbstractDatabaseAppender<T extends AbstractDatabaseManager> extends AbstractAppender {
    039        private static final long serialVersionUID = 1L;
    040
    041        private final ReadWriteLock lock = new ReentrantReadWriteLock();
    042        private final Lock readLock = lock.readLock();
    043        private final Lock writeLock = lock.writeLock();
    044
    045        private T manager;
    046
    047        /**
    048         * Instantiates the base appender.
    049         *
    050         * @param name The appender name.
    051         * @param filter The filter, if any, to use.
    052         * @param ignoreExceptions If {@code true} exceptions encountered when appending events are logged; otherwise
    053         *                         they are propagated to the caller.
    054         * @param manager The matching {@link AbstractDatabaseManager} implementation.
    055         */
    056        protected AbstractDatabaseAppender(final String name, final Filter filter, final boolean ignoreExceptions,
    057                                           final T manager) {
    058            super(name, filter, null, ignoreExceptions);
    059            this.manager = manager;
    060        }
    061
    062        /**
    063         * This always returns {@code null}, as database appenders do not use a single layout. The JPA and NoSQL appenders
    064         * do not use a layout at all. The JDBC appender has a layout-per-column pattern.
    065         *
    066         * @return {@code null}.
    067         */
    068        @Override
    069        public final Layout<LogEvent> getLayout() {
    070            return null;
    071        }
    072
    073        /**
    074         * Returns the underlying manager in use within this appender.
    075         *
    076         * @return the manager.
    077         */
    078        public final T getManager() {
    079            return this.manager;
    080        }
    081
    082        @Override
    083        public final void start() {
    084            if (this.getManager() == null) {
    085                LOGGER.error("No AbstractDatabaseManager set for the appender named [{}].", this.getName());
    086            }
    087            super.start();
    088            if (this.getManager() != null) {
    089                this.getManager().startup();
    090            }
    091        }
    092
    093        @Override
    094        public final void stop() {
    095            super.stop();
    096            if (this.getManager() != null) {
    097                this.getManager().release();
    098            }
    099        }
    100
    101        @Override
    102        public final void append(final LogEvent event) {
    103            this.readLock.lock();
    104            try {
    105                this.getManager().write(event);
    106            } catch (final LoggingException e) {
    107                LOGGER.error("Unable to write to database [{}] for appender [{}].", this.getManager().getName(),
    108                        this.getName(), e);
    109                throw e;
    110            } catch (final Exception e) {
    111                LOGGER.error("Unable to write to database [{}] for appender [{}].", this.getManager().getName(),
    112                        this.getName(), e);
    113                throw new AppenderLoggingException("Unable to write to database in appender: " + e.getMessage(), e);
    114            } finally {
    115                this.readLock.unlock();
    116            }
    117        }
    118
    119        /**
    120         * Replaces the underlying manager in use within this appender. This can be useful for manually changing the way log
    121         * events are written to the database without losing buffered or in-progress events. The existing manager is
    122         * released only after the new manager has been installed. This method is thread-safe.
    123         *
    124         * @param manager The new manager to install.
    125         */
    126        protected final void replaceManager(final T manager) {
    127            this.writeLock.lock();
    128            try {
    129                final T old = this.getManager();
    130                if (!manager.isRunning()) {
    131                    manager.startup();
    132                }
    133                this.manager = manager;
    134                old.release();
    135            } finally {
    136                this.writeLock.unlock();
    137            }
    138        }
    139    }