1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.spf4j.log;
17
18 import com.fasterxml.jackson.core.JsonFactory;
19 import com.fasterxml.jackson.core.JsonGenerator;
20 import com.fasterxml.jackson.databind.ObjectMapper;
21 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
22 import java.io.IOException;
23 import java.io.UncheckedIOException;
24 import java.time.Instant;
25 import java.util.Collections;
26 import java.util.HashSet;
27 import java.util.Set;
28 import javax.annotation.Nonnull;
29 import javax.annotation.Nullable;
30 import javax.annotation.ParametersAreNonnullByDefault;
31 import javax.annotation.concurrent.ThreadSafe;
32 import org.slf4j.Marker;
33 import org.spf4j.base.Arrays;
34 import org.spf4j.base.JsonWriteable;
35 import org.spf4j.base.Slf4jMessageFormatter;
36 import org.spf4j.base.Throwables;
37 import org.spf4j.io.AppendableWriter;
38 import org.spf4j.io.ObjectAppenderSupplier;
39
40
41
42
43
44 @SuppressFBWarnings("LO_SUSPECT_LOG_PARAMETER")
45 @ParametersAreNonnullByDefault
46 @ThreadSafe
47 public class Slf4jLogRecordImpl implements JsonWriteable, Slf4jLogRecord {
48
49 private final String threadName;
50 private final String loggerName;
51 private final Level level;
52 private final long timeStamp;
53 private final Marker marker;
54 private final String messageFormat;
55 private final Object[] arguments;
56 private volatile int startExtra;
57 @Nullable
58 private volatile String message;
59 private volatile boolean isLogged;
60 private Set<Object> attachments;
61
62
63 public Slf4jLogRecordImpl(final String logger, final Level level,
64 final String format, final Object... arguments) {
65 this(logger, level, null, format, arguments);
66 }
67
68 public Slf4jLogRecordImpl(final String logger, final Level level,
69 @Nullable final Marker marker, final String format, final Object... arguments) {
70 this(false, logger, level, marker, System.currentTimeMillis(), format, arguments);
71 }
72
73 public Slf4jLogRecordImpl(final boolean isLogged, final String logger, final Level level,
74 @Nullable final Marker marker, final String format, final Object... arguments) {
75 this(isLogged, logger, level, marker, System.currentTimeMillis(), format, arguments);
76 }
77
78 @SuppressFBWarnings("EI_EXPOSE_REP2")
79 public Slf4jLogRecordImpl(final boolean isLogged, final String logger, final Level level,
80 @Nullable final Marker marker, final long timestampMillis,
81 final String format, final Object... arguments) {
82 this.loggerName = logger;
83 this.level = level;
84 this.timeStamp = timestampMillis;
85 this.marker = marker;
86 this.messageFormat = format;
87 this.arguments = arguments;
88 this.threadName = Thread.currentThread().getName();
89 this.startExtra = -1;
90 this.message = null;
91 this.isLogged = isLogged;
92 this.attachments = Collections.EMPTY_SET;
93 }
94
95 @Override
96 public final String getLoggerName() {
97 return loggerName;
98 }
99
100 @Override
101 public final Level getLevel() {
102 return level;
103 }
104
105 @Override
106 public final long getTimeStamp() {
107 return timeStamp;
108 }
109
110 @Nullable
111 @Override
112 @SuppressFBWarnings("EI_EXPOSE_REP")
113 public final Marker getMarker() {
114 return marker;
115 }
116
117 @Override
118 public final String getMessageFormat() {
119 return messageFormat;
120 }
121
122 @SuppressFBWarnings("EI_EXPOSE_REP")
123 @Nonnull
124 @Override
125 public final Object[] getArguments() {
126 return arguments;
127 }
128
129 @Override
130 public final int getNrMessageArguments() {
131 int sx = this.startExtra;
132 if (sx < 0) {
133 sx = Slf4jMessageFormatter.getFormatParameterNumber(messageFormat);
134 this.startExtra = sx;
135 }
136 return sx;
137 }
138
139 @Override
140 public final String getThreadName() {
141 return threadName;
142 }
143
144 @Nonnull
145 @Override
146 public final String getMessage() {
147 materializeMessage();
148 return message;
149 }
150
151 private void materializeMessage() {
152 if (message == null) {
153 synchronized (messageFormat) {
154 if (message == null) {
155 StringBuilder sb = new StringBuilder(messageFormat.length() + arguments.length * 8);
156 try {
157 this.startExtra = Slf4jMessageFormatter.format(0, sb, messageFormat,
158 ObjectAppenderSupplier.TO_STRINGER, arguments);
159 } catch (IOException ex) {
160 throw new UncheckedIOException(ex);
161 }
162 message = sb.toString();
163 }
164 }
165 }
166 }
167
168 @Nonnull
169 @Override
170 public final Object[] getExtraArgumentsRaw() {
171 int sx = getNrMessageArguments();
172 if (sx < arguments.length) {
173 return java.util.Arrays.copyOfRange(arguments, sx, arguments.length);
174 } else {
175 return Arrays.EMPTY_OBJ_ARRAY;
176 }
177 }
178
179 @Nonnull
180 @Override
181 public final Object[] getExtraArguments() {
182 int sx = getNrMessageArguments();
183 if (sx < arguments.length) {
184 int nrExtraThrowables = getNrExtraThrowables();
185 if (nrExtraThrowables <= 0) {
186 return java.util.Arrays.copyOfRange(arguments, sx, arguments.length);
187 } else {
188 Object[] result = new Object[arguments.length - sx - nrExtraThrowables];
189 int i = 0;
190 for (int j = sx; j < arguments.length; j++) {
191 Object argument = arguments[j];
192 if (!(argument instanceof Throwable)) {
193 result[i++] = argument;
194 }
195 }
196 return result;
197 }
198 } else {
199 return Arrays.EMPTY_OBJ_ARRAY;
200 }
201 }
202
203 private int getNrExtraThrowables() {
204 int sx = getNrMessageArguments();
205 int count = 0;
206 for (int i = sx; i < arguments.length; i++) {
207 Object argument = arguments[i];
208 if (argument instanceof Throwable) {
209 count++;
210 }
211 }
212 return count;
213 }
214
215 @Nullable
216 @Override
217 public final Throwable getExtraThrowable() {
218 int sx = getNrMessageArguments();
219 Throwable result = null;
220 for (int i = sx; i < arguments.length; i++) {
221 Object argument = arguments[i];
222 if (argument instanceof Throwable) {
223 if (result == null) {
224 result = (Throwable) argument;
225 } else {
226 Throwables.suppressLimited(result, (Throwable) argument);
227 }
228 }
229 }
230 return result;
231 }
232
233
234
235
236
237 @Override
238 public String toString() {
239 StringBuilder sb = new StringBuilder(64);
240 writeTo(sb);
241 return sb.toString();
242 }
243
244
245
246
247
248 @Override
249 public void writeJsonTo(final Appendable appendable) throws IOException {
250 JsonGenerator gen = Lazy.JSON.createGenerator(new AppendableWriter(appendable));
251 gen.setCodec(Lazy.MAPPER);
252 gen.writeStartObject();
253 gen.writeFieldName("ts");
254 gen.writeString(Instant.ofEpochMilli(timeStamp).toString());
255 gen.writeFieldName("logger");
256 gen.writeString(loggerName);
257 gen.writeFieldName("thread");
258 gen.writeString(threadName);
259 gen.writeFieldName("msg");
260 gen.writeString(getMessage());
261 Object[] extraArguments = getExtraArguments();
262 if (extraArguments.length > 0) {
263 gen.writeFieldName("xObj");
264 gen.writeStartArray();
265 for (Object obj : extraArguments) {
266 gen.writeObject(obj);
267 }
268 gen.writeEndArray();
269 }
270 Throwable t = getExtraThrowable();
271 if (t != null) {
272 gen.writeFieldName("throwable");
273 gen.writeString(Throwables.toString(t));
274 }
275 gen.writeEndObject();
276 gen.flush();
277 }
278
279 @Override
280 public final boolean isLogged() {
281 return isLogged;
282 }
283
284 @Override
285 public final void setIsLogged() {
286 isLogged = true;
287 }
288
289 @Override
290 public final synchronized void attach(final Object obj) {
291 if (attachments.isEmpty()) {
292 attachments = new HashSet<>(2);
293 }
294 attachments.add(obj);
295 }
296
297 @Override
298 public final synchronized boolean hasAttachment(final Object obj) {
299 return attachments.contains(obj);
300 }
301
302 @Override
303 public final synchronized Set<Object> getAttachments() {
304 return attachments.isEmpty() ? attachments : Collections.unmodifiableSet(attachments);
305 }
306
307 private static final class Lazy {
308
309 private static final JsonFactory JSON = new JsonFactory();
310
311 private static final ObjectMapper MAPPER = new ObjectMapper(JSON);
312 }
313
314
315 }