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;
018
019import java.util.concurrent.TimeUnit;
020import java.util.concurrent.locks.Lock;
021import java.util.concurrent.locks.ReadWriteLock;
022import java.util.concurrent.locks.ReentrantReadWriteLock;
023
024import org.apache.logging.log4j.LoggingException;
025import org.apache.logging.log4j.core.Filter;
026import org.apache.logging.log4j.core.Layout;
027import org.apache.logging.log4j.core.LogEvent;
028import org.apache.logging.log4j.core.appender.AbstractAppender;
029import org.apache.logging.log4j.core.appender.AppenderLoggingException;
030
031/**
032 * An abstract Appender for writing events to a database of some type, be it relational or NoSQL. All database appenders
033 * should inherit from this base appender. Three implementations are currently provided:
034 * {@link org.apache.logging.log4j.core.appender.db.jdbc JDBC}, {@link org.apache.logging.log4j.core.appender.db.jpa
035 * JPA}, and <a href="/log4j/2.x/log4j-nosql/apidocs/">NoSQL</a>.
036 *
037 * @param <T> Specifies which type of {@link AbstractDatabaseManager} this Appender requires.
038 */
039public abstract class AbstractDatabaseAppender<T extends AbstractDatabaseManager> extends AbstractAppender {
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 boolean stop(final long timeout, final TimeUnit timeUnit) {
095        setStopping();
096        boolean stopped = super.stop(timeout, timeUnit, false);
097        if (this.getManager() != null) {
098            stopped &= this.getManager().stop(timeout, timeUnit);
099        }
100        setStopped();
101        return stopped;
102    }
103
104    @Override
105    public final void append(final LogEvent event) {
106        this.readLock.lock();
107        try {
108            this.getManager().write(event);
109        } catch (final LoggingException e) {
110            LOGGER.error("Unable to write to database [{}] for appender [{}].", this.getManager().getName(),
111                    this.getName(), e);
112            throw e;
113        } catch (final Exception e) {
114            LOGGER.error("Unable to write to database [{}] for appender [{}].", this.getManager().getName(),
115                    this.getName(), e);
116            throw new AppenderLoggingException("Unable to write to database in appender: " + e.getMessage(), e);
117        } finally {
118            this.readLock.unlock();
119        }
120    }
121
122    /**
123     * Replaces the underlying manager in use within this appender. This can be useful for manually changing the way log
124     * events are written to the database without losing buffered or in-progress events. The existing manager is
125     * released only after the new manager has been installed. This method is thread-safe.
126     *
127     * @param manager The new manager to install.
128     */
129    protected final void replaceManager(final T manager) {
130        this.writeLock.lock();
131        try {
132            final T old = this.getManager();
133            if (!manager.isRunning()) {
134                manager.startup();
135            }
136            this.manager = manager;
137            old.close();
138        } finally {
139            this.writeLock.unlock();
140        }
141    }
142}