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    }