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.nosql;
018
019import java.util.Map;
020
021import org.apache.logging.log4j.Marker;
022import org.apache.logging.log4j.ThreadContext;
023import org.apache.logging.log4j.core.LogEvent;
024import org.apache.logging.log4j.core.appender.AppenderLoggingException;
025import org.apache.logging.log4j.core.appender.ManagerFactory;
026import org.apache.logging.log4j.core.appender.db.AbstractDatabaseManager;
027
028/**
029 * An {@link AbstractDatabaseManager} implementation for all NoSQL databases.
030 *
031 * @param <W> A type parameter for reassuring the compiler that all operations are using the same {@link NoSQLObject}.
032 */
033public final class NoSQLDatabaseManager<W> extends AbstractDatabaseManager {
034    private static final NoSQLDatabaseManagerFactory FACTORY = new NoSQLDatabaseManagerFactory();
035
036    private final NoSQLProvider<NoSQLConnection<W, ? extends NoSQLObject<W>>> provider;
037
038    private NoSQLConnection<W, ? extends NoSQLObject<W>> connection;
039
040    private NoSQLDatabaseManager(final String name, final int bufferSize,
041            final NoSQLProvider<NoSQLConnection<W, ? extends NoSQLObject<W>>> provider) {
042        super(name, bufferSize);
043        this.provider = provider;
044    }
045
046    @Override
047    protected void startupInternal() {
048        // nothing to see here
049    }
050
051    @Override
052    protected void shutdownInternal() {
053        if (this.connection != null) {
054            this.commitAndClose();
055        }
056    }
057
058    @Override
059    protected void connectAndStart() {
060        try {
061            this.connection = this.provider.getConnection();
062        } catch (Exception e) {
063            throw new AppenderLoggingException("Failed to get connection from NoSQL connection provider.", e);
064        }
065    }
066
067    @Override
068    protected void writeInternal(final LogEvent event) {
069        if (!this.isRunning() || this.connection == null || this.connection.isClosed()) {
070            throw new AppenderLoggingException(
071                    "Cannot write logging event; NoSQL manager not connected to the database.");
072        }
073
074        final NoSQLObject<W> entity = this.connection.createObject();
075        entity.set("level", event.getLevel());
076        entity.set("loggerName", event.getLoggerName());
077        entity.set("message", event.getMessage() == null ? null : event.getMessage().getFormattedMessage());
078
079        final StackTraceElement source = event.getSource();
080        if (source == null) {
081            entity.set("source", (Object) null);
082        } else {
083            entity.set("source", this.convertStackTraceElement(source));
084        }
085
086        Marker marker = event.getMarker();
087        if (marker == null) {
088            entity.set("marker", (Object) null);
089        } else {
090            final NoSQLObject<W> originalMarkerEntity = this.connection.createObject();
091            NoSQLObject<W> markerEntity = originalMarkerEntity;
092            markerEntity.set("name", marker.getName());
093            while (marker.getParent() != null) {
094                marker = marker.getParent();
095                final NoSQLObject<W> parentMarkerEntity = this.connection.createObject();
096                parentMarkerEntity.set("name", marker.getName());
097                markerEntity.set("parent", parentMarkerEntity);
098                markerEntity = parentMarkerEntity;
099            }
100            entity.set("marker", originalMarkerEntity);
101        }
102
103        entity.set("threadName", event.getThreadName());
104        entity.set("millis", event.getMillis());
105        entity.set("date", new java.util.Date(event.getMillis()));
106
107        @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
108        Throwable thrown = event.getThrown();
109        if (thrown == null) {
110            entity.set("thrown", (Object) null);
111        } else {
112            final NoSQLObject<W> originalExceptionEntity = this.connection.createObject();
113            NoSQLObject<W> exceptionEntity = originalExceptionEntity;
114            exceptionEntity.set("type", thrown.getClass().getName());
115            exceptionEntity.set("message", thrown.getMessage());
116            exceptionEntity.set("stackTrace", this.convertStackTrace(thrown.getStackTrace()));
117            while (thrown.getCause() != null) {
118                thrown = thrown.getCause();
119                final NoSQLObject<W> causingExceptionEntity = this.connection.createObject();
120                causingExceptionEntity.set("type", thrown.getClass().getName());
121                causingExceptionEntity.set("message", thrown.getMessage());
122                causingExceptionEntity.set("stackTrace", this.convertStackTrace(thrown.getStackTrace()));
123                exceptionEntity.set("cause", causingExceptionEntity);
124                exceptionEntity = causingExceptionEntity;
125            }
126
127            entity.set("thrown", originalExceptionEntity);
128        }
129
130        final Map<String, String> contextMap = event.getContextMap();
131        if (contextMap == null) {
132            entity.set("contextMap", (Object) null);
133        } else {
134            final NoSQLObject<W> contextMapEntity = this.connection.createObject();
135            for (final Map.Entry<String, String> entry : contextMap.entrySet()) {
136                contextMapEntity.set(entry.getKey(), entry.getValue());
137            }
138            entity.set("contextMap", contextMapEntity);
139        }
140
141        final ThreadContext.ContextStack contextStack = event.getContextStack();
142        if (contextStack == null) {
143            entity.set("contextStack", (Object) null);
144        } else {
145            entity.set("contextStack", contextStack.asList().toArray());
146        }
147
148        this.connection.insertObject(entity);
149    }
150
151    @Override
152    protected void commitAndClose() {
153        try {
154            if (this.connection != null && !this.connection.isClosed()) {
155                this.connection.close();
156            }
157        } catch (Exception e) {
158            throw new AppenderLoggingException("Failed to commit and close NoSQL connection in manager.", e);
159        }
160    }
161
162    private NoSQLObject<W>[] convertStackTrace(final StackTraceElement[] stackTrace) {
163        final NoSQLObject<W>[] stackTraceEntities = this.connection.createList(stackTrace.length);
164        for (int i = 0; i < stackTrace.length; i++) {
165            stackTraceEntities[i] = this.convertStackTraceElement(stackTrace[i]);
166        }
167        return stackTraceEntities;
168    }
169
170    private NoSQLObject<W> convertStackTraceElement(final StackTraceElement element) {
171        final NoSQLObject<W> elementEntity = this.connection.createObject();
172        elementEntity.set("className", element.getClassName());
173        elementEntity.set("methodName", element.getMethodName());
174        elementEntity.set("fileName", element.getFileName());
175        elementEntity.set("lineNumber", element.getLineNumber());
176        return elementEntity;
177    }
178
179    /**
180     * Creates a NoSQL manager for use within the {@link NoSQLAppender}, or returns a suitable one if it already exists.
181     *
182     * @param name The name of the manager, which should include connection details and hashed passwords where possible.
183     * @param bufferSize The size of the log event buffer.
184     * @param provider A provider instance which will be used to obtain connections to the chosen NoSQL database.
185     * @return a new or existing NoSQL manager as applicable.
186     */
187    public static NoSQLDatabaseManager<?> getNoSQLDatabaseManager(final String name, final int bufferSize,
188                                                                  final NoSQLProvider<?> provider) {
189        return AbstractDatabaseManager.getManager(name, new FactoryData(bufferSize, provider), FACTORY);
190    }
191
192    /**
193     * Encapsulates data that {@link NoSQLDatabaseManagerFactory} uses to create managers.
194     */
195    private static final class FactoryData extends AbstractDatabaseManager.AbstractFactoryData {
196        private final NoSQLProvider<?> provider;
197
198        protected FactoryData(final int bufferSize, final NoSQLProvider<?> provider) {
199            super(bufferSize);
200            this.provider = provider;
201        }
202    }
203
204    /**
205     * Creates managers.
206     */
207    private static final class NoSQLDatabaseManagerFactory implements
208            ManagerFactory<NoSQLDatabaseManager<?>, FactoryData> {
209        @Override
210        @SuppressWarnings("unchecked")
211        public NoSQLDatabaseManager<?> createManager(final String name, final FactoryData data) {
212            return new NoSQLDatabaseManager(name, data.getBufferSize(), data.provider);
213        }
214    }
215}