    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     */
    017    package org.apache.logging.log4j.nosql.appender;
    018
    019    import java.util.Map;
    020
    021    import org.apache.logging.log4j.Marker;
    022    import org.apache.logging.log4j.ThreadContext;
    023    import org.apache.logging.log4j.core.LogEvent;
    024    import org.apache.logging.log4j.core.appender.AppenderLoggingException;
    025    import org.apache.logging.log4j.core.appender.ManagerFactory;
    026    import org.apache.logging.log4j.core.appender.db.AbstractDatabaseManager;
    027    import org.apache.logging.log4j.core.util.Closer;
    028
    029    /**
    030     * An {@link AbstractDatabaseManager} implementation for all NoSQL databases.
    031     *
    032     * @param <W> A type parameter for reassuring the compiler that all operations are using the same {@link NoSqlObject}.
    033     */
    034    public final class NoSqlDatabaseManager<W> extends AbstractDatabaseManager {
    035        private static final NoSQLDatabaseManagerFactory FACTORY = new NoSQLDatabaseManagerFactory();
    036
    037        private final NoSqlProvider<NoSqlConnection<W, ? extends NoSqlObject<W>>> provider;
    038
    039        private NoSqlConnection<W, ? extends NoSqlObject<W>> connection;
    040
    041        private NoSqlDatabaseManager(final String name, final int bufferSize,
    042                final NoSqlProvider<NoSqlConnection<W, ? extends NoSqlObject<W>>> provider) {
    043            super(name, bufferSize);
    044            this.provider = provider;
    045        }
    046
    047        @Override
    048        protected void startupInternal() {
    049            // nothing to see here
    050        }
    051
    052        @Override
    053        protected void shutdownInternal() {
    054            // NoSQL doesn't use transactions, so all we need to do here is simply close the client
    055            Closer.closeSilently(this.connection);
    056        }
    057
    058        @Override
    059        protected void connectAndStart() {
    060            try {
    061                this.connection = this.provider.getConnection();
    062            } catch (final 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            final Marker marker = event.getMarker();
    087            if (marker == null) {
    088                entity.set("marker", (Object) null);
    089            } else {
    090                entity.set("marker", buildMarkerEntity(marker));
    091            }
    092
    093            entity.set("threadName", event.getThreadName());
    094            entity.set("millis", event.getTimeMillis());
    095            entity.set("date", new java.util.Date(event.getTimeMillis()));
    096
    097            @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
    098            Throwable thrown = event.getThrown();
    099            if (thrown == null) {
    100                entity.set("thrown", (Object) null);
    101            } else {
    102                final NoSqlObject<W> originalExceptionEntity = this.connection.createObject();
    103                NoSqlObject<W> exceptionEntity = originalExceptionEntity;
    104                exceptionEntity.set("type", thrown.getClass().getName());
    105                exceptionEntity.set("message", thrown.getMessage());
    106                exceptionEntity.set("stackTrace", this.convertStackTrace(thrown.getStackTrace()));
    107                while (thrown.getCause() != null) {
    108                    thrown = thrown.getCause();
    109                    final NoSqlObject<W> causingExceptionEntity = this.connection.createObject();
    110                    causingExceptionEntity.set("type", thrown.getClass().getName());
    111                    causingExceptionEntity.set("message", thrown.getMessage());
    112                    causingExceptionEntity.set("stackTrace", this.convertStackTrace(thrown.getStackTrace()));
    113                    exceptionEntity.set("cause", causingExceptionEntity);
    114                    exceptionEntity = causingExceptionEntity;
    115                }
    116
    117                entity.set("thrown", originalExceptionEntity);
    118            }
    119
    120            final Map<String, String> contextMap = event.getContextMap();
    121            if (contextMap == null) {
    122                entity.set("contextMap", (Object) null);
    123            } else {
    124                final NoSqlObject<W> contextMapEntity = this.connection.createObject();
    125                for (final Map.Entry<String, String> entry : contextMap.entrySet()) {
    126                    contextMapEntity.set(entry.getKey(), entry.getValue());
    127                }
    128                entity.set("contextMap", contextMapEntity);
    129            }
    130
    131            final ThreadContext.ContextStack contextStack = event.getContextStack();
    132            if (contextStack == null) {
    133                entity.set("contextStack", (Object) null);
    134            } else {
    135                entity.set("contextStack", contextStack.asList().toArray());
    136            }
    137
    138            this.connection.insertObject(entity);
    139        }
    140
    141        private NoSqlObject<W> buildMarkerEntity(final Marker marker) {
    142            final NoSqlObject<W> entity = this.connection.createObject();
    143            entity.set("name", marker.getName());
    144
    145            final Marker[] parents = marker.getParents();
    146            if (parents != null) {
    147                @SuppressWarnings("unchecked")
    148                final NoSqlObject<W>[] parentEntities = new NoSqlObject[parents.length];
    149                for (int i = 0; i < parents.length; i++) {
    150                    parentEntities[i] = buildMarkerEntity(parents[i]);
    151                }
    152                entity.set("parents", parentEntities);
    153            }
    154            return entity;
    155        }
    156
    157        @Override
    158        protected void commitAndClose() {
    159            // all NoSQL drivers auto-commit (since NoSQL doesn't generally use the concept of transactions).
    160            // also, all our NoSQL drivers use internal connection pooling and provide clients, not connections.
    161            // thus, we should not be closing the client until shutdown as NoSQL is very different from SQL.
    162            // see LOG4J2-591 and LOG4J2-676
    163        }
    164
    165        private NoSqlObject<W>[] convertStackTrace(final StackTraceElement[] stackTrace) {
    166            final NoSqlObject<W>[] stackTraceEntities = this.connection.createList(stackTrace.length);
    167            for (int i = 0; i < stackTrace.length; i++) {
    168                stackTraceEntities[i] = this.convertStackTraceElement(stackTrace[i]);
    169            }
    170            return stackTraceEntities;
    171        }
    172
    173        private NoSqlObject<W> convertStackTraceElement(final StackTraceElement element) {
    174            final NoSqlObject<W> elementEntity = this.connection.createObject();
    175            elementEntity.set("className", element.getClassName());
    176            elementEntity.set("methodName", element.getMethodName());
    177            elementEntity.set("fileName", element.getFileName());
    178            elementEntity.set("lineNumber", element.getLineNumber());
    179            return elementEntity;
    180        }
    181
    182        /**
    183         * Creates a NoSQL manager for use within the {@link NoSqlAppender}, or returns a suitable one if it already exists.
    184         *
    185         * @param name The name of the manager, which should include connection details and hashed passwords where possible.
    186         * @param bufferSize The size of the log event buffer.
    187         * @param provider A provider instance which will be used to obtain connections to the chosen NoSQL database.
    188         * @return a new or existing NoSQL manager as applicable.
    189         */
    190        public static NoSqlDatabaseManager<?> getNoSqlDatabaseManager(final String name, final int bufferSize,
    191                                                                      final NoSqlProvider<?> provider) {
    192            return AbstractDatabaseManager.getManager(name, new FactoryData(bufferSize, provider), FACTORY);
    193        }
    194
    195        /**
    196         * Encapsulates data that {@link NoSQLDatabaseManagerFactory} uses to create managers.
    197         */
    198        private static final class FactoryData extends AbstractDatabaseManager.AbstractFactoryData {
    199            private final NoSqlProvider<?> provider;
    200
    201            protected FactoryData(final int bufferSize, final NoSqlProvider<?> provider) {
    202                super(bufferSize);
    203                this.provider = provider;
    204            }
    205        }
    206
    207        /**
    208         * Creates managers.
    209         */
    210        private static final class NoSQLDatabaseManagerFactory implements
    211                ManagerFactory<NoSqlDatabaseManager<?>, FactoryData> {
    212            @Override
    213            @SuppressWarnings("unchecked")
    214            public NoSqlDatabaseManager<?> createManager(final String name, final FactoryData data) {
    215                return new NoSqlDatabaseManager(name, data.getBufferSize(), data.provider);
    216            }
    217        }
    218    }