View Javadoc
1   /*
2    * Copyright 2018 SPF4J.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
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   * @author Zoltan Farkas
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") // risk I take...
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    * can be sub-classed to change the string representation.
235    * @return
236    */
237   @Override
238   public String toString() {
239     StringBuilder sb = new StringBuilder(64);
240     writeTo(sb);
241     return sb.toString();
242   }
243 
244   /**
245    * can be sub-classed to change the json representation.
246    * @return
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 }