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 {@link org.apache.logging.log4j.nosql.appender NoSQL}.
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 final ReadWriteLock lock = new ReentrantReadWriteLock();
040        private final Lock readLock = lock.readLock();
041        private final Lock writeLock = lock.writeLock();
042    
043        private T manager;
044    
045        /**
046         * Instantiates the base appender.
047         *
048         * @param name The appender name.
049         * @param filter The filter, if any, to use.
050         * @param ignoreExceptions If {@code true} exceptions encountered when appending events are logged; otherwise
051         *                         they are propagated to the caller.
052         * @param manager The matching {@link AbstractDatabaseManager} implementation.
053         */
054        protected AbstractDatabaseAppender(final String name, final Filter filter, final boolean ignoreExceptions,
055                                           final T manager) {
056            super(name, filter, null, ignoreExceptions);
057            this.manager = manager;
058        }
059    
060        /**
061         * This always returns {@code null}, as database appenders do not use a single layout. The JPA and NoSQL appenders
062         * do not use a layout at all. The JDBC appender has a layout-per-column pattern.
063         *
064         * @return {@code null}.
065         */
066        @Override
067        public final Layout<LogEvent> getLayout() {
068            return null;
069        }
070    
071        /**
072         * Returns the underlying manager in use within this appender.
073         *
074         * @return the manager.
075         */
076        public final T getManager() {
077            return this.manager;
078        }
079    
080        @Override
081        public final void start() {
082            if (this.getManager() == null) {
083                LOGGER.error("No AbstractDatabaseManager set for the appender named [{}].", this.getName());
084            }
085            super.start();
086            if (this.getManager() != null) {
087                this.getManager().startup();
088            }
089        }
090    
091        @Override
092        public final void stop() {
093            super.stop();
094            if (this.getManager() != null) {
095                this.getManager().release();
096            }
097        }
098    
099        @Override
100        public final void append(final LogEvent event) {
101            this.readLock.lock();
102            try {
103                this.getManager().write(event);
104            } catch (final LoggingException e) {
105                LOGGER.error("Unable to write to database [{}] for appender [{}].", this.getManager().getName(),
106                        this.getName(), e);
107                throw e;
108            } catch (final Exception e) {
109                LOGGER.error("Unable to write to database [{}] for appender [{}].", this.getManager().getName(),
110                        this.getName(), e);
111                throw new AppenderLoggingException("Unable to write to database in appender: " + e.getMessage(), e);
112            } finally {
113                this.readLock.unlock();
114            }
115        }
116    
117        /**
118         * Replaces the underlying manager in use within this appender. This can be useful for manually changing the way log
119         * events are written to the database without losing buffered or in-progress events. The existing manager is
120         * released only after the new manager has been installed. This method is thread-safe.
121         *
122         * @param manager The new manager to install.
123         */
124        protected final void replaceManager(final T manager) {
125            this.writeLock.lock();
126            try {
127                final T old = this.getManager();
128                if (!manager.isRunning()) {
129                    manager.startup();
130                }
131                this.manager = manager;
132                old.release();
133            } finally {
134                this.writeLock.unlock();
135            }
136        }
137    }