001/*
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2026, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v2.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.classic.spi;
015
016import java.io.IOException;
017import java.io.InvalidObjectException;
018import java.io.ObjectInputStream;
019import java.io.ObjectOutputStream;
020import java.io.Serializable;
021import java.time.Instant;
022import java.util.ArrayDeque;
023import java.util.Collections;
024import java.util.Deque;
025import java.util.IdentityHashMap;
026import java.util.List;
027import java.util.Map;
028import java.util.Set;
029
030import org.slf4j.Marker;
031import org.slf4j.event.KeyValuePair;
032import org.slf4j.helpers.MessageFormatter;
033
034import ch.qos.logback.classic.Level;
035
036// http://www.riehle.org/computer-science/research/1998/ubilab-tr-1998-10-1.html
037// See also the paper https://www.riehle.org/computer-science/research/1998/ubilab-tr-1998-10-1.pdf
038
039/**
040 * A read-only and serializable implementation of {@link ILoggingEvent}.
041 * 
042 * @author Ceki Gülcü
043 * @since 0.9.16
044 */
045public class LoggingEventVO implements ILoggingEvent, Serializable {
046
047    private static final long serialVersionUID = 6553722650255690312L;
048
049    private static final int NULL_ARGUMENT_ARRAY = -1;
050    private static final String NULL_ARGUMENT_ARRAY_ELEMENT = "NULL_ARGUMENT_ARRAY_ELEMENT";
051    private static final int ARGUMENT_ARRAY_DESERIALIZATION_LIMIT = 128;
052
053    private String threadName;
054    private String loggerName;
055    private LoggerContextVO loggerContextVO;
056
057    private transient Level level;
058    private String message;
059
060    // we gain significant space at serialization time by marking
061    // formattedMessage as transient and constructing it lazily in
062    // getFormattedMessage()
063    private transient String formattedMessage;
064
065    private transient Object[] argumentArray;
066
067    private ThrowableProxyVO throwableProxy;
068    private StackTraceElement[] callerDataArray;
069    private List<Marker> markerList;
070    private List<KeyValuePair> keyValuePairList;
071    private Map<String, String> mdcPropertyMap;
072
073    private long timestamp;
074    private int nanoseconds;
075
076    private long sequenceNumber;
077
078    public static LoggingEventVO build(ILoggingEvent le) {
079        LoggingEventVO ledo = new LoggingEventVO();
080        ledo.loggerName = le.getLoggerName();
081        ledo.loggerContextVO = le.getLoggerContextVO();
082        ledo.threadName = le.getThreadName();
083        ledo.level = (le.getLevel());
084        ledo.message = (le.getMessage());
085        ledo.argumentArray = (le.getArgumentArray());
086        ledo.markerList = le.getMarkerList();
087        ledo.keyValuePairList = le.getKeyValuePairs();
088        ledo.mdcPropertyMap = le.getMDCPropertyMap();
089        ledo.timestamp = le.getTimeStamp();
090        ledo.nanoseconds = le.getNanoseconds();
091        ledo.sequenceNumber = le.getSequenceNumber();
092        ledo.throwableProxy = ThrowableProxyVO.build(le.getThrowableProxy());
093        // add caller data only if it is there already
094        // See also https://jira.qos.ch/browse/LOGBACK-480
095        if (le.hasCallerData()) {
096            ledo.callerDataArray = le.getCallerData();
097        }
098        return ledo;
099    }
100
101    public String getThreadName() {
102        return threadName;
103    }
104
105    public LoggerContextVO getLoggerContextVO() {
106        return loggerContextVO;
107    }
108
109    public String getLoggerName() {
110        return loggerName;
111    }
112
113    public Level getLevel() {
114        return level;
115    }
116
117    public String getMessage() {
118        return message;
119    }
120
121    public String getFormattedMessage() {
122        if (formattedMessage != null) {
123            return formattedMessage;
124        }
125
126        if (argumentArray != null) {
127            formattedMessage = MessageFormatter.arrayFormat(message, argumentArray).getMessage();
128        } else {
129            formattedMessage = message;
130        }
131
132        return formattedMessage;
133    }
134
135    public Object[] getArgumentArray() {
136        return argumentArray;
137    }
138
139    public IThrowableProxy getThrowableProxy() {
140        return throwableProxy;
141    }
142
143    // for tests only
144    void setThrowableProxyVO(ThrowableProxyVO tpVO) {
145        this.throwableProxy = tpVO;
146    }
147
148    public StackTraceElement[] getCallerData() {
149        return callerDataArray;
150    }
151
152    public boolean hasCallerData() {
153        return callerDataArray != null;
154    }
155
156    public List<Marker> getMarkerList() {
157        return markerList;
158    }
159
160    @Override
161    public long getTimeStamp() { return timestamp; }
162
163    @Override
164    public int getNanoseconds() {  return nanoseconds; }
165
166    public long getSequenceNumber() {
167        return sequenceNumber;
168    }
169
170    public long getContextBirthTime() {
171        return loggerContextVO.getBirthTime();
172    }
173
174    public LoggerContextVO getContextLoggerRemoteView() {
175        return loggerContextVO;
176    }
177
178    public Map<String, String> getMDCPropertyMap() {
179        return mdcPropertyMap;
180    }
181
182    public Map<String, String> getMdc() {
183        return mdcPropertyMap;
184    }
185
186    @Override
187    public List<KeyValuePair> getKeyValuePairs() {
188        return this.keyValuePairList;
189    }
190
191    public void prepareForDeferredProcessing() {
192    }
193
194    private void writeObject(ObjectOutputStream out) throws IOException {
195        out.defaultWriteObject();
196        out.writeInt(level.levelInt);
197        if (argumentArray != null) {
198            int len = argumentArray.length;
199            out.writeInt(len);
200            for (int i = 0; i < argumentArray.length; i++) {
201                if (argumentArray[i] != null) {
202                    out.writeObject(argumentArray[i].toString());
203                } else {
204                    out.writeObject(NULL_ARGUMENT_ARRAY_ELEMENT);
205                }
206            }
207        } else {
208            out.writeInt(NULL_ARGUMENT_ARRAY);
209        }
210
211    }
212
213    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
214        in.defaultReadObject();
215        int levelInt = in.readInt();
216        level = Level.toLevel(levelInt);
217
218        int argArrayLen = in.readInt();
219
220        // Prevent DOS attacks via large or negative arrays
221        if (argArrayLen < NULL_ARGUMENT_ARRAY || argArrayLen > ARGUMENT_ARRAY_DESERIALIZATION_LIMIT) {
222            throw new InvalidObjectException("Argument array length is invalid: " + argArrayLen);
223        }
224
225        if (argArrayLen != NULL_ARGUMENT_ARRAY) {
226            argumentArray = new String[argArrayLen];
227            for (int i = 0; i < argArrayLen; i++) {
228                Object val = in.readObject();
229                if (!NULL_ARGUMENT_ARRAY_ELEMENT.equals(val)) {
230                    argumentArray[i] = val;
231                }
232            }
233        }
234
235        // ThrowableProxyVO.build() always yields a tree. A crafted stream can make a cause or
236        // suppressed entry refer back to another proxy, which would send renderers into an endless loop
237        if (throwableProxy != null && !isTree(throwableProxy)) {
238            throw new InvalidObjectException("Throwable proxy contains a cycle or a shared reference");
239        }
240    }
241
242    private static boolean isTree(IThrowableProxy root) {
243        Set<IThrowableProxy> seen = Collections.newSetFromMap(new IdentityHashMap<>());
244        Deque<IThrowableProxy> pending = new ArrayDeque<>();
245        pending.push(root);
246        while (!pending.isEmpty()) {
247            IThrowableProxy tp = pending.pop();
248            if (!seen.add(tp)) {
249                return false;
250            }
251            if (tp.getCause() != null) {
252                pending.push(tp.getCause());
253            }
254            IThrowableProxy[] suppressed = tp.getSuppressed();
255            if (suppressed != null) {
256                for (IThrowableProxy s : suppressed) {
257                    if (s != null) {
258                        pending.push(s);
259                    }
260                }
261            }
262        }
263        return true;
264    }
265
266    @Override
267    public int hashCode() {
268        final int prime = 31;
269        long millis = getTimeStamp();
270
271        int result = 1;
272        result = prime * result + ((message == null) ? 0 : message.hashCode());
273        result = prime * result + ((threadName == null) ? 0 : threadName.hashCode());
274        result = prime * result + (int) (millis ^ (millis >>> 32));
275        return result;
276    }
277
278    @Override
279    public boolean equals(Object obj) {
280        if (this == obj)
281            return true;
282        if (obj == null)
283            return false;
284        if (getClass() != obj.getClass())
285            return false;
286        final LoggingEventVO other = (LoggingEventVO) obj;
287        if (message == null) {
288            if (other.message != null)
289                return false;
290        } else if (!message.equals(other.message))
291            return false;
292
293        if (loggerName == null) {
294            if (other.loggerName != null)
295                return false;
296        } else if (!loggerName.equals(other.loggerName))
297            return false;
298
299        if (threadName == null) {
300            if (other.threadName != null)
301                return false;
302        } else if (!threadName.equals(other.threadName))
303            return false;
304        if (getTimeStamp() != other.getTimeStamp())
305            return false;
306
307        if (markerList == null) {
308            if (other.markerList != null)
309                return false;
310        } else if (!markerList.equals(other.markerList))
311            return false;
312
313        if (mdcPropertyMap == null) {
314            if (other.mdcPropertyMap != null)
315                return false;
316        } else if (!mdcPropertyMap.equals(other.mdcPropertyMap))
317            return false;
318        return true;
319    }
320
321}