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}