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    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}