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