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.async;
018
019import java.io.IOException;
020import java.util.HashMap;
021import java.util.Map;
022
023import org.apache.logging.log4j.Level;
024import org.apache.logging.log4j.Marker;
025import org.apache.logging.log4j.ThreadContext.ContextStack;
026import org.apache.logging.log4j.core.LogEvent;
027import org.apache.logging.log4j.core.config.Property;
028import org.apache.logging.log4j.core.impl.Log4jLogEvent;
029import org.apache.logging.log4j.core.impl.ThrowableProxy;
030import org.apache.logging.log4j.core.lookup.StrSubstitutor;
031import org.apache.logging.log4j.message.Message;
032import org.apache.logging.log4j.message.SimpleMessage;
033import org.apache.logging.log4j.util.Strings;
034
035import 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 */
041public class RingBufferLogEvent implements LogEvent {
042
043    /** The {@code EventFactory} for {@code RingBufferLogEvent}s. */
044    public static final Factory FACTORY = new Factory();
045
046    private static final long serialVersionUID = 8462119088943934758L;
047
048    /**
049     * Creates the events that will be put in the RingBuffer.
050     */
051    private static class Factory implements EventFactory<RingBufferLogEvent> {
052
053        @Override
054        public RingBufferLogEvent newInstance() {
055            return new RingBufferLogEvent();
056        }
057    }
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    private long nanoTime;
075
076    public void setValues(final AsyncLogger anAsyncLogger, final String aLoggerName, final Marker aMarker,
077            final String theFqcn, final Level aLevel, final Message msg, final Throwable aThrowable,
078            final Map<String, String> aMap, final ContextStack aContextStack, final String aThreadName,
079            final StackTraceElement aLocation, final long aCurrentTimeMillis, final long aNanoTime) {
080        this.asyncLogger = anAsyncLogger;
081        this.loggerName = aLoggerName;
082        this.marker = aMarker;
083        this.fqcn = theFqcn;
084        this.level = aLevel;
085        this.message = msg;
086        this.thrown = aThrowable;
087        this.thrownProxy = null;
088        this.contextMap = aMap;
089        this.contextStack = aContextStack;
090        this.threadName = aThreadName;
091        this.location = aLocation;
092        this.currentTimeMillis = aCurrentTimeMillis;
093        this.nanoTime = aNanoTime;
094    }
095
096    /**
097     * Event processor that reads the event from the ringbuffer can call this method.
098     * 
099     * @param endOfBatch flag to indicate if this is the last event in a batch from the RingBuffer
100     */
101    public void execute(final boolean endOfBatch) {
102        this.endOfBatch = endOfBatch;
103        asyncLogger.actualAsyncLog(this);
104    }
105
106    /**
107     * Returns {@code true} if this event is the end of a batch, {@code false} otherwise.
108     * 
109     * @return {@code true} if this event is the end of a batch, {@code false} otherwise
110     */
111    @Override
112    public boolean isEndOfBatch() {
113        return endOfBatch;
114    }
115
116    @Override
117    public void setEndOfBatch(final boolean endOfBatch) {
118        this.endOfBatch = endOfBatch;
119    }
120
121    @Override
122    public boolean isIncludeLocation() {
123        return includeLocation;
124    }
125
126    @Override
127    public void setIncludeLocation(final boolean includeLocation) {
128        this.includeLocation = includeLocation;
129    }
130
131    @Override
132    public String getLoggerName() {
133        return loggerName;
134    }
135
136    @Override
137    public Marker getMarker() {
138        return marker;
139    }
140
141    @Override
142    public String getLoggerFqcn() {
143        return fqcn;
144    }
145
146    @Override
147    public Level getLevel() {
148        if (level == null) {
149            level = Level.OFF; // LOG4J2-462, LOG4J2-465
150        }
151        return level;
152    }
153
154    @Override
155    public Message getMessage() {
156        if (message == null) {
157            message = new SimpleMessage(Strings.EMPTY);
158        }
159        return message;
160    }
161
162    @Override
163    public Throwable getThrown() {
164        // after deserialization, thrown is null but thrownProxy may be non-null
165        if (thrown == null) {
166            if (thrownProxy != null) {
167                thrown = thrownProxy.getThrowable();
168            }
169        }
170        return thrown;
171    }
172
173    @Override
174    public ThrowableProxy getThrownProxy() {
175        // lazily instantiate the (expensive) ThrowableProxy
176        if (thrownProxy == null) {
177            if (thrown != null) {
178                thrownProxy = new ThrowableProxy(thrown);
179            }
180        }
181        return this.thrownProxy;
182    }
183
184    @Override
185    public Map<String, String> getContextMap() {
186        return contextMap;
187    }
188
189    @Override
190    public ContextStack getContextStack() {
191        return contextStack;
192    }
193
194    @Override
195    public String getThreadName() {
196        return threadName;
197    }
198
199    @Override
200    public StackTraceElement getSource() {
201        return location;
202    }
203
204    @Override
205    public long getTimeMillis() {
206        return currentTimeMillis;
207    }
208    
209    @Override
210    public long getNanoTime() {
211        return nanoTime;
212    }
213
214    /**
215     * Merges the contents of the specified map into the contextMap, after replacing any variables in the property
216     * values with the StrSubstitutor-supplied actual values.
217     * 
218     * @param properties configured properties
219     * @param strSubstitutor used to lookup values of variables in properties
220     */
221    public void mergePropertiesIntoContextMap(final Map<Property, Boolean> properties,
222            final StrSubstitutor strSubstitutor) {
223        if (properties == null) {
224            return; // nothing to do
225        }
226
227        final Map<String, String> map = contextMap == null ? new HashMap<String, String>()
228                : new HashMap<>(contextMap);
229
230        for (final Map.Entry<Property, Boolean> entry : properties.entrySet()) {
231            final Property prop = entry.getKey();
232            if (map.containsKey(prop.getName())) {
233                continue; // contextMap overrides config properties
234            }
235            final String value = entry.getValue().booleanValue() ? strSubstitutor.replace(prop.getValue()) : prop
236                    .getValue();
237            map.put(prop.getName(), value);
238        }
239        contextMap = map;
240    }
241
242    /**
243     * Release references held by ring buffer to allow objects to be garbage-collected.
244     */
245    public void clear() {
246        setValues(null, // asyncLogger
247                null, // loggerName
248                null, // marker
249                null, // fqcn
250                null, // level
251                null, // data
252                null, // t
253                null, // map
254                null, // contextStack
255                null, // threadName
256                null, // location
257                0, // currentTimeMillis
258                0 // nanoTime
259        );
260    }
261
262    private void writeObject(final java.io.ObjectOutputStream out) throws IOException {
263        getThrownProxy(); // initialize the ThrowableProxy before serializing
264        out.defaultWriteObject();
265    }
266
267    /**
268     * Creates and returns a new immutable copy of this {@code RingBufferLogEvent}.
269     *
270     * @return a new immutable copy of the data in this {@code RingBufferLogEvent}
271     */
272    public LogEvent createMemento() {
273        final LogEvent result = new Log4jLogEvent.Builder(this).build();
274        return result;
275    }
276
277    /**
278     * Initializes the specified {@code Log4jLogEvent.Builder} from this {@code RingBufferLogEvent}.
279     * @param builder the builder whose fields to populate
280     */
281    public void initializeBuilder(Log4jLogEvent.Builder builder) {
282        builder.setContextMap(contextMap) //
283                .setContextStack(contextStack) //
284                .setEndOfBatch(endOfBatch) //
285                .setIncludeLocation(includeLocation) //
286                .setLevel(getLevel()) // ensure non-null
287                .setLoggerFqcn(fqcn) //
288                .setLoggerName(loggerName) //
289                .setMarker(marker) //
290                .setMessage(getMessage()) // ensure non-null
291                .setNanoTime(nanoTime) //
292                .setSource(location) //
293                .setThreadName(threadName) //
294                .setThrown(getThrown()) // may deserialize from thrownProxy
295                .setThrownProxy(thrownProxy) // avoid unnecessarily creating thrownProxy
296                .setTimeMillis(currentTimeMillis);
297    }
298}