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