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