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.core.async;
018    
019    import java.io.IOException;
020    import java.util.HashMap;
021    import java.util.Map;
022    
023    import org.apache.logging.log4j.Level;
024    import org.apache.logging.log4j.Marker;
025    import org.apache.logging.log4j.ThreadContext.ContextStack;
026    import org.apache.logging.log4j.core.LogEvent;
027    import org.apache.logging.log4j.core.config.Property;
028    import org.apache.logging.log4j.core.impl.Log4jLogEvent;
029    import org.apache.logging.log4j.core.impl.ThrowableProxy;
030    import org.apache.logging.log4j.core.lookup.StrSubstitutor;
031    import org.apache.logging.log4j.message.Message;
032    import org.apache.logging.log4j.message.SimpleMessage;
033    import org.apache.logging.log4j.util.Strings;
034    
035    import com.lmax.disruptor.EventFactory;
036    
037    /**
038     * When the Disruptor is started, the RingBuffer is populated with event objects. These objects are then re-used during
039     * the life of the RingBuffer.
040     */
041    public class RingBufferLogEvent implements LogEvent {
042        private static final long serialVersionUID = 8462119088943934758L;
043    
044        /**
045         * Creates the events that will be put in the RingBuffer.
046         */
047        private static class Factory implements EventFactory<RingBufferLogEvent> {
048    
049            @Override
050            public RingBufferLogEvent newInstance() {
051                return new RingBufferLogEvent();
052            }
053        }
054    
055        /** The {@code EventFactory} for {@code RingBufferLogEvent}s. */
056        public static final Factory FACTORY = new Factory();
057    
058        private transient AsyncLogger asyncLogger;
059        private String loggerName;
060        private Marker marker;
061        private String fqcn;
062        private Level level;
063        private Message message;
064        private transient Throwable thrown;
065        private ThrowableProxy thrownProxy;
066        private Map<String, String> contextMap;
067        private ContextStack contextStack;
068        private String threadName;
069        private StackTraceElement location;
070        private long currentTimeMillis;
071        private boolean endOfBatch;
072        private boolean includeLocation;
073    
074        public void setValues(final AsyncLogger asyncLogger, final String loggerName, final Marker marker,
075                final String fqcn, final Level level, final Message data, final Throwable throwable,
076                final Map<String, String> map, final ContextStack contextStack, final String threadName,
077                final StackTraceElement location, final long currentTimeMillis) {
078            this.asyncLogger = asyncLogger;
079            this.loggerName = loggerName;
080            this.marker = marker;
081            this.fqcn = fqcn;
082            this.level = level;
083            this.message = data;
084            this.thrown = throwable;
085            this.thrownProxy = null;
086            this.contextMap = map;
087            this.contextStack = contextStack;
088            this.threadName = threadName;
089            this.location = location;
090            this.currentTimeMillis = currentTimeMillis;
091        }
092    
093        /**
094         * Event processor that reads the event from the ringbuffer can call this method.
095         * 
096         * @param endOfBatch flag to indicate if this is the last event in a batch from the RingBuffer
097         */
098        public void execute(final boolean endOfBatch) {
099            this.endOfBatch = endOfBatch;
100            asyncLogger.actualAsyncLog(this);
101        }
102    
103        /**
104         * Returns {@code true} if this event is the end of a batch, {@code false} otherwise.
105         * 
106         * @return {@code true} if this event is the end of a batch, {@code false} otherwise
107         */
108        @Override
109        public boolean isEndOfBatch() {
110            return endOfBatch;
111        }
112    
113        @Override
114        public void setEndOfBatch(final boolean endOfBatch) {
115            this.endOfBatch = endOfBatch;
116        }
117    
118        @Override
119        public boolean isIncludeLocation() {
120            return includeLocation;
121        }
122    
123        @Override
124        public void setIncludeLocation(final boolean includeLocation) {
125            this.includeLocation = includeLocation;
126        }
127    
128        @Override
129        public String getLoggerName() {
130            return loggerName;
131        }
132    
133        @Override
134        public Marker getMarker() {
135            return marker;
136        }
137    
138        @Override
139        public String getLoggerFqcn() {
140            return fqcn;
141        }
142    
143        @Override
144        public Level getLevel() {
145            if (level == null) {
146                level = Level.OFF; // LOG4J2-462, LOG4J2-465
147            }
148            return level;
149        }
150    
151        @Override
152        public Message getMessage() {
153            if (message == null) {
154                message = new SimpleMessage(Strings.EMPTY);
155            }
156            return message;
157        }
158    
159        @Override
160        public Throwable getThrown() {
161            // after deserialization, thrown is null but thrownProxy may be non-null
162            if (thrown == null) {
163                if (thrownProxy != null) {
164                    thrown = thrownProxy.getThrowable();
165                }
166            }
167            return thrown;
168        }
169    
170        @Override
171        public ThrowableProxy getThrownProxy() {
172            // lazily instantiate the (expensive) ThrowableProxy
173            if (thrownProxy == null) {
174                if (thrown != null) {
175                    thrownProxy = new ThrowableProxy(thrown);
176                }
177            }
178            return this.thrownProxy;
179        }
180    
181        @Override
182        public Map<String, String> getContextMap() {
183            return contextMap;
184        }
185    
186        @Override
187        public ContextStack getContextStack() {
188            return contextStack;
189        }
190    
191        @Override
192        public String getThreadName() {
193            return threadName;
194        }
195    
196        @Override
197        public StackTraceElement getSource() {
198            return location;
199        }
200    
201        @Override
202        public long getTimeMillis() {
203            return currentTimeMillis;
204        }
205    
206        /**
207         * Merges the contents of the specified map into the contextMap, after replacing any variables in the property
208         * values with the StrSubstitutor-supplied actual values.
209         * 
210         * @param properties configured properties
211         * @param strSubstitutor used to lookup values of variables in properties
212         */
213        public void mergePropertiesIntoContextMap(final Map<Property, Boolean> properties,
214                final StrSubstitutor strSubstitutor) {
215            if (properties == null) {
216                return; // nothing to do
217            }
218    
219            final Map<String, String> map = contextMap == null ? new HashMap<String, String>()
220                    : new HashMap<String, String>(contextMap);
221    
222            for (final Map.Entry<Property, Boolean> entry : properties.entrySet()) {
223                final Property prop = entry.getKey();
224                if (map.containsKey(prop.getName())) {
225                    continue; // contextMap overrides config properties
226                }
227                final String value = entry.getValue().booleanValue() ? strSubstitutor.replace(prop.getValue()) : prop
228                        .getValue();
229                map.put(prop.getName(), value);
230            }
231            contextMap = map;
232        }
233    
234        /**
235         * Release references held by ring buffer to allow objects to be garbage-collected.
236         */
237        public void clear() {
238            setValues(null, // asyncLogger
239                    null, // loggerName
240                    null, // marker
241                    null, // fqcn
242                    null, // level
243                    null, // data
244                    null, // t
245                    null, // map
246                    null, // contextStack
247                    null, // threadName
248                    null, // location
249                    0 // currentTimeMillis
250            );
251        }
252    
253        private void writeObject(final java.io.ObjectOutputStream out) throws IOException {
254            getThrownProxy(); // initialize the ThrowableProxy before serializing
255            out.defaultWriteObject();
256        }
257    
258        /**
259         * Creates and returns a new immutable copy of this {@code RingBufferLogEvent}.
260         * 
261         * @return a new immutable copy of the data in this {@code RingBufferLogEvent}
262         */
263        public LogEvent createMemento() {
264            // Ideally, would like to use the LogEventFactory here but signature does not match:
265            // results in factory re-creating the timestamp, context map and context stack, which we don't want.
266            return new Log4jLogEvent(loggerName, marker, fqcn, level, message, thrown, contextMap, contextStack,
267                    threadName, location, currentTimeMillis);
268        }
269    }