    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    }