001/**
002 * Copyright (c) 2004-2022 QOS.ch
003 * All rights reserved.
004 * <p>
005 * Permission is hereby granted, free  of charge, to any person obtaining
006 * a  copy  of this  software  and  associated  documentation files  (the
007 * "Software"), to  deal in  the Software without  restriction, including
008 * without limitation  the rights to  use, copy, modify,  merge, publish,
009 * distribute,  sublicense, and/or sell  copies of  the Software,  and to
010 * permit persons to whom the Software  is furnished to do so, subject to
011 * the following conditions:
012 * <p>
013 * The  above  copyright  notice  and  this permission  notice  shall  be
014 * included in all copies or substantial portions of the Software.
015 * <p>
016 * THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
017 * EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
018 * MERCHANTABILITY,    FITNESS    FOR    A   PARTICULAR    PURPOSE    AND
019 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
020 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
021 * OF CONTRACT, TORT OR OTHERWISE,  ARISING FROM, OUT OF OR IN CONNECTION
022 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
023 */
024package org.slf4j.spi;
025
026import java.util.List;
027import java.util.function.Supplier;
028
029import org.slf4j.Logger;
030import org.slf4j.Marker;
031import org.slf4j.event.DefaultLoggingEvent;
032import org.slf4j.event.KeyValuePair;
033import org.slf4j.event.Level;
034import org.slf4j.event.LoggingEvent;
035import org.slf4j.helpers.Reporter;
036
037/**
038 * Default implementation of {@link LoggingEventBuilder}
039 */
040public class DefaultLoggingEventBuilder implements LoggingEventBuilder, CallerBoundaryAware {
041
042
043    // The caller boundary when the log() methods are invoked, is this class itself.
044
045    static String DLEB_FQCN = DefaultLoggingEventBuilder.class.getName();
046
047    protected DefaultLoggingEvent loggingEvent;
048    protected Logger logger;
049
050    public DefaultLoggingEventBuilder(Logger logger, Level level) {
051        this.logger = logger;
052        loggingEvent = new DefaultLoggingEvent(level, logger);
053    }
054
055    /**
056     * Add a marker to the current logging event being built.
057     * <p>
058     * It is possible to add multiple markers to the same logging event.
059     *
060     * @param marker the marker to add
061     */
062    @Override
063    public LoggingEventBuilder addMarker(Marker marker) {
064        loggingEvent.addMarker(marker);
065        return this;
066    }
067
068    @Override
069    public LoggingEventBuilder setCause(Throwable t) {
070        loggingEvent.setThrowable(t);
071        return this;
072    }
073
074    @Override
075    public LoggingEventBuilder addArgument(Object p) {
076        this.loggingEvent.addArgument(p);
077        return this;
078    }
079
080    @Override
081    public LoggingEventBuilder addArgument(Supplier<?> objectSupplier) {
082        this.loggingEvent.addArgument(objectSupplier.get());
083        return this;
084    }
085
086    @Override
087    public LoggingEventBuilder addKeyValue(String key, Object value) {
088        loggingEvent.addKeyValue(key, value);
089        return this;
090    }
091
092    @Override
093    public LoggingEventBuilder addKeyValue(String key, Supplier<Object> value) {
094        loggingEvent.addKeyValue(key, value.get());
095        return this;
096    }
097
098    @Override
099    public void setCallerBoundary(String fqcn) {
100        this.loggingEvent.setCallerBoundary(fqcn);
101    }
102
103    @Override
104    public void log() {
105        log(this.loggingEvent);
106    }
107
108    @Override
109    public LoggingEventBuilder setMessage(String message) {
110        this.loggingEvent.setMessage(message);
111        return this;
112    }
113
114    @Override
115    public LoggingEventBuilder setMessage(Supplier<String> messageSupplier) {
116        this.loggingEvent.setMessage(messageSupplier.get());
117        return this;
118    }
119
120    @Override
121    public void log(String message) {
122        this.loggingEvent.setMessage(message);
123        log(this.loggingEvent);
124    }
125
126    @Override
127    public void log(String message, Object arg) {
128        this.loggingEvent.setMessage(message);
129        this.loggingEvent.addArgument(arg);
130        log(this.loggingEvent);
131    }
132
133    @Override
134    public void log(String message, Object arg0, Object arg1) {
135        this.loggingEvent.setMessage(message);
136        this.loggingEvent.addArgument(arg0);
137        this.loggingEvent.addArgument(arg1);
138        log(this.loggingEvent);
139    }
140
141    @Override
142    public void log(String message, Object... args) {
143        this.loggingEvent.setMessage(message);
144        this.loggingEvent.addArguments(args);
145
146        log(this.loggingEvent);
147    }
148
149    @Override
150    public void log(Supplier<String> messageSupplier) {
151        if(messageSupplier == null) {
152            log((String) null);
153        } else {
154            log(messageSupplier.get());
155        }
156    }
157
158    protected void log(LoggingEvent aLoggingEvent) {
159        if(aLoggingEvent.getCallerBoundary() == null) {
160            setCallerBoundary(DLEB_FQCN);
161        }
162
163        if(logger instanceof LoggingEventAware) {
164            ((LoggingEventAware) logger).log(aLoggingEvent);
165        } else if(logger instanceof LocationAwareLogger) {
166            logViaLocationAwareLoggerAPI((LocationAwareLogger) logger, aLoggingEvent);
167        } else {
168            logViaPublicSLF4JLoggerAPI(aLoggingEvent);
169        }
170    }
171
172    private void logViaLocationAwareLoggerAPI(LocationAwareLogger locationAwareLogger, LoggingEvent aLoggingEvent) {
173        List<Marker> markerList = aLoggingEvent.getMarkers();
174        Marker firstMarker = (markerList != null && !markerList.isEmpty()) ? markerList.get(0) : null;
175        String mergedMessage = mergeMarkersAndKeyValuePairsAndMessage(aLoggingEvent);
176        locationAwareLogger.log(firstMarker, aLoggingEvent.getCallerBoundary(), aLoggingEvent.getLevel().toInt(),
177                                mergedMessage,
178                                aLoggingEvent.getArgumentArray(), aLoggingEvent.getThrowable());
179    }
180
181    private void logViaPublicSLF4JLoggerAPI(LoggingEvent aLoggingEvent) {
182        Object[] argArray = aLoggingEvent.getArgumentArray();
183        int argLen = argArray == null ? 0 : argArray.length;
184
185        Throwable t = aLoggingEvent.getThrowable();
186        int tLen = t == null ? 0 : 1;
187
188        Object[] combinedArguments = new Object[argLen + tLen];
189
190        if(argArray != null) {
191            System.arraycopy(argArray, 0, combinedArguments, 0, argLen);
192        }
193        if(t != null) {
194            combinedArguments[argLen] = t;
195        }
196
197        String mergedMessage = mergeMarkersAndKeyValuePairsAndMessage(aLoggingEvent);
198
199
200        switch(aLoggingEvent.getLevel()) {
201            case TRACE:
202                logger.trace(mergedMessage, combinedArguments);
203                break;
204            case DEBUG:
205                logger.debug(mergedMessage, combinedArguments);
206                break;
207            case INFO:
208                logger.info(mergedMessage, combinedArguments);
209                break;
210            case WARN:
211                logger.warn(mergedMessage, combinedArguments);
212                break;
213            case ERROR:
214                logger.error(mergedMessage, combinedArguments);
215                break;
216        }
217    }
218
219
220    /**
221     * Prepend markers and key-value pairs to the message.
222     *
223     * @param aLoggingEvent
224     *
225     * @return
226     */
227    private String mergeMarkersAndKeyValuePairsAndMessage(LoggingEvent aLoggingEvent) {
228        StringBuilder sb = mergeMarkers(aLoggingEvent.getMarkers(), null);
229        sb = mergeKeyValuePairs(aLoggingEvent.getKeyValuePairs(), sb);
230        final String mergedMessage = mergeMessage(aLoggingEvent.getMessage(), sb);
231        return mergedMessage;
232    }
233
234    private StringBuilder mergeMarkers(List<Marker> markerList, StringBuilder sb) {
235        if(markerList == null || markerList.isEmpty())
236            return sb;
237
238        if(sb == null)
239            sb = new StringBuilder();
240
241        for(Marker marker : markerList) {
242            sb.append(marker);
243            sb.append(' ');
244        }
245        return sb;
246    }
247
248    private StringBuilder mergeKeyValuePairs(List<KeyValuePair> keyValuePairList, StringBuilder sb) {
249        if(keyValuePairList == null || keyValuePairList.isEmpty())
250            return sb;
251
252        if(sb == null)
253            sb = new StringBuilder();
254
255        for(KeyValuePair kvp : keyValuePairList) {
256            sb.append(kvp.key);
257            sb.append('=');
258            // Same protection as MessageFormatter.safeObjectAppend: a failing
259            // toString() (including StackOverflowError) must not abort logging.
260            // See https://github.com/qos-ch/slf4j/issues/448
261            safeObjectAppend(sb, kvp.value);
262            sb.append(' ');
263        }
264        return sb;
265    }
266
267    /**
268     * Append {@code o} to {@code sb}, catching any {@link Throwable} thrown by
269     * {@link Object#toString()} and substituting {@code [FAILED toString()]}.
270     * Mirrors {@code MessageFormatter.safeObjectAppend}.
271     */
272    private static void safeObjectAppend(StringBuilder sb, Object o) {
273        if (o == null) {
274            sb.append("null");
275            return;
276        }
277        try {
278            sb.append(o.toString());
279        } catch (Throwable t) {
280            Reporter.error("Failed toString() invocation on an object of type [" + o.getClass().getName() + "]", t);
281            sb.append("[FAILED toString()]");
282        }
283    }
284
285    private String mergeMessage(String msg, StringBuilder sb) {
286        if(sb != null) {
287            sb.append(msg);
288            return sb.toString();
289        } else {
290            return msg;
291        }
292    }
293
294
295
296
297
298
299}