1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache license, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the license for the specific language governing permissions and
15 * limitations under the license.
16 */
17 package org.apache.logging.log4j.core.appender.db;
18
19 import java.io.Serializable;
20 import java.util.concurrent.TimeUnit;
21 import java.util.concurrent.locks.Lock;
22 import java.util.concurrent.locks.ReadWriteLock;
23 import java.util.concurrent.locks.ReentrantReadWriteLock;
24
25 import org.apache.logging.log4j.LoggingException;
26 import org.apache.logging.log4j.core.Filter;
27 import org.apache.logging.log4j.core.Layout;
28 import org.apache.logging.log4j.core.LogEvent;
29 import org.apache.logging.log4j.core.appender.AbstractAppender;
30 import org.apache.logging.log4j.core.appender.AppenderLoggingException;
31
32 /**
33 * An abstract Appender for writing events to a database of some type, be it relational or NoSQL. All database appenders
34 * should inherit from this base appender. Three implementations are currently provided:
35 * {@link org.apache.logging.log4j.core.appender.db.jdbc JDBC}, {@link org.apache.logging.log4j.core.appender.db.jpa
36 * JPA}, and {@link org.apache.logging.log4j.core.appender.nosql NoSQL}.
37 *
38 * @param <T> Specifies which type of {@link AbstractDatabaseManager} this Appender requires.
39 */
40 public abstract class AbstractDatabaseAppender<T extends AbstractDatabaseManager> extends AbstractAppender {
41
42 private final ReadWriteLock lock = new ReentrantReadWriteLock();
43 private final Lock readLock = lock.readLock();
44 private final Lock writeLock = lock.writeLock();
45
46 private T manager;
47
48 /**
49 * Instantiates the base appender.
50 *
51 * @param name The appender name.
52 * @param filter The filter, if any, to use.
53 * @param ignoreExceptions If {@code true} exceptions encountered when appending events are logged; otherwise
54 * they are propagated to the caller.
55 * @param manager The matching {@link AbstractDatabaseManager} implementation.
56 */
57 protected AbstractDatabaseAppender(final String name, final Filter filter, final boolean ignoreExceptions,
58 final T manager) {
59 super(name, filter, null, ignoreExceptions);
60 this.manager = manager;
61 }
62
63 /**
64 * Instantiates the base appender.
65 *
66 * @param name The appender name.
67 * @param filter The filter, if any, to use.
68 * @param layout The layout to use to format the event.
69 * @param ignoreExceptions If {@code true} exceptions encountered when appending events are logged; otherwise
70 * they are propagated to the caller.
71 * @param manager The matching {@link AbstractDatabaseManager} implementation.
72 */
73 protected AbstractDatabaseAppender(final String name, final Filter filter,
74 final Layout<? extends Serializable> layout, final boolean ignoreExceptions, final T manager) {
75 super(name, filter, layout, ignoreExceptions);
76 this.manager = manager;
77 }
78
79 /**
80 * This always returns {@code null}, as database appenders do not use a single layout. The JPA and NoSQL appenders
81 * do not use a layout at all. The JDBC appender has a layout-per-column pattern.
82 *
83 * @return {@code null}.
84 */
85 @Override
86 public final Layout<LogEvent> getLayout() {
87 return null;
88 }
89
90 /**
91 * Returns the underlying manager in use within this appender.
92 *
93 * @return the manager.
94 */
95 public final T getManager() {
96 return this.manager;
97 }
98
99 @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 }