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.locks.Lock;
020import java.util.concurrent.locks.ReadWriteLock;
021import java.util.concurrent.locks.ReentrantReadWriteLock;
022
023import org.apache.logging.log4j.LoggingException;
024import org.apache.logging.log4j.core.Filter;
025import org.apache.logging.log4j.core.Layout;
026import org.apache.logging.log4j.core.LogEvent;
027import org.apache.logging.log4j.core.appender.AbstractAppender;
028import 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.core.appender.db.nosql NoSQL}.
035 *
036 * @param <T> Specifies which type of {@link AbstractDatabaseManager} this Appender requires.
037 */
038public 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}