All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.reactiverse.contextual.logging.jul.JULContextualDataFormatter Maven / Gradle / Ivy

/*
 * Copyright 2024 Red Hat, Inc.
 *
 * Red Hat licenses this file to you under the Apache License, version 2.0
 * (the "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at:
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package io.reactiverse.contextual.logging.jul;

import io.reactiverse.contextual.logging.ContextualData;
import io.vertx.core.internal.ContextInternal;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.function.BiFunction;
import java.util.logging.Formatter;
import java.util.logging.LogManager;
import java.util.logging.LogRecord;

/**
 * JUL Formatted that is able to process Vert.x Context data. Besides the format used in the parent class,
 * vert.x context variables can be referred by name too. For simplicity the default variables are also exposed
 * by name and cannot be overridden.
 * 

* {@inheritDoc} * * @author Paulo Lopes */ public final class JULContextualDataFormatter extends Formatter { private static final String placeholderPrefix = "%{"; private static final String placeholderSuffix = "}"; private static final String defaultEmpty = ""; private final String template; private final Date dat = new Date(); private static final List RESERVED = Arrays.asList("date", "source", "logger", "level", "message", "thrown"); private final List> resolvers = new ArrayList<>(); public JULContextualDataFormatter() { this(LogManager.getLogManager().getProperty(JULContextualDataFormatter.class.getName() + ".format")); } JULContextualDataFormatter(String template) { if (template == null) { // default to the JDK default template = "%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp %2$s%n%4$s: %5$s%6$s%n"; } // add the default resolvers // 1. date resolvers.add((record, ctx) -> { // with java 11 this should be replaced with the new time APIs dat.setTime(record.getMillis()); return dat; }); // 2. source resolvers.add((record, ctx) -> { String source; if (record.getSourceClassName() != null) { source = record.getSourceClassName(); if (record.getSourceMethodName() != null) { source = source + " " + record.getSourceMethodName(); } } else { source = record.getLoggerName(); } return source; }); // 3. logger resolvers.add((record, ctx) -> record.getLoggerName()); // 4. level resolvers.add((record, ctx) -> record.getLevel().getLocalizedName()); // 5. message resolvers.add((record, ctx) -> formatMessage(record)); // 6. thrown resolvers.add((record, ctx) -> { String throwable = defaultEmpty; if (record.getThrown() != null) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); pw.println(); record.getThrown().printStackTrace(pw); pw.close(); throwable = sw.toString(); } return throwable; }); this.template = parseStringValue(template); } private String parseStringValue(String template) { StringBuilder buf = new StringBuilder(template); int startIndex = template.indexOf(placeholderPrefix); while (startIndex != -1) { int endIndex = findPlaceholderEndIndex(buf, startIndex); if (endIndex != -1) { String placeholder = buf.substring(startIndex + placeholderPrefix.length(), endIndex); int index = RESERVED.indexOf(placeholder); if (index == -1) { // lookup default value int defIndex = placeholder.indexOf(":-"); // need to lock to use inside lambda final String defValue; final String ctxKey; if (defIndex != -1) { ctxKey = placeholder.substring(0, defIndex); defValue = placeholder.substring(defIndex + 2); } else { defValue = defaultEmpty; ctxKey = placeholder; } // placeholder is not present so we need to compute it at runtime resolvers.add((record, ctx) -> { if (ctx != null) { return ContextualData.getOrDefault(ctxKey, defValue); } else { return defValue; } }); index = resolvers.size(); } String sIndex = "%" + index; buf.replace(startIndex, endIndex + placeholderSuffix.length(), sIndex); startIndex = buf.indexOf(placeholderPrefix, startIndex + sIndex.length()); } else { startIndex = -1; } } // the final transformed template final String format = buf.toString(); // validate that the format string is valid try { final Object[] args = new Object[resolvers.size()]; // fill with dummy data args[0] = new Date(); args[1] = ""; args[2] = ""; args[3] = ""; args[4] = ""; args[5] = null; // fill the custom resolvers for (int i = 6; i < args.length; i++) { args[i] = null; } // if the format fails the template is invalid. This is also the technique used in the JDK however // the JDK will silently fall back to the default format in this case it makes sense to abort as the // behavior would not match what the users desire. String.format(format, args); } catch (RuntimeException e) { throw new IllegalArgumentException("format string \"" + template + "\" is not valid."); } return format; } private static int findPlaceholderEndIndex(CharSequence buf, int startIndex) { int index = startIndex + placeholderPrefix.length(); int withinNestedPlaceholder = 0; while (index < buf.length()) { if (substringMatch(buf, index, placeholderSuffix)) { if (withinNestedPlaceholder > 0) { withinNestedPlaceholder--; index = index + placeholderPrefix.length() - 1; } else { return index; } } else if (substringMatch(buf, index, placeholderPrefix)) { withinNestedPlaceholder++; index = index + placeholderPrefix.length(); } else { index++; } } return -1; } private static boolean substringMatch(CharSequence str, int index, CharSequence substring) { for (int j = 0; j < substring.length(); j++) { int i = index + j; if (i >= str.length() || str.charAt(i) != substring.charAt(j)) { return false; } } return true; } @Override public String format(LogRecord record) { final Object[] args = new Object[resolvers.size()]; final ContextInternal context = ContextInternal.current(); // process the placeholder values for (int i = 0; i < args.length; i++) { args[i] = resolvers.get(i).apply(record, context); } // format return String.format(template, args); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy