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    }