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

org.apache.juneau.microservice.resources.LogEntryFormatter Maven / Gradle / Ivy

// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership.  The ASF 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 org.apache.juneau.microservice.resources;

import static org.apache.juneau.internal.CollectionUtils.*;

import java.text.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import java.util.logging.*;
import java.util.logging.Formatter;
import java.util.regex.*;

import org.apache.juneau.common.internal.*;

/**
 * Log entry formatter.
 *
 * 

* Uses three simple parameter for configuring log entry formats: *

    *
  • * dateFormat - A {@link SimpleDateFormat} string describing the format for dates. *
  • * format - A string with {...} replacement variables representing predefined fields. *
  • * useStackTraceHashes - A setting that causes duplicate stack traces to be replaced with 8-character * hash strings. *
* *

* This class converts the format strings into a regular expression that can be used to parse the resulting log file. */ public class LogEntryFormatter extends Formatter { private ConcurrentHashMap hashes; private DateFormat df; private String format; private Pattern rePattern; private Map fieldIndexes; /** * Create a new formatter. * * @param format * The log entry format. e.g. "[{date} {level}] {msg}%n" * The string can contain any of the following variables: *

    *
  1. "{date}" - The date, formatted per "Logging/dateFormat". *
  2. "{class}" - The class name. *
  3. "{method}" - The method name. *
  4. "{logger}" - The logger name. *
  5. "{level}" - The log level name. *
  6. "{msg}" - The log message. *
  7. "{threadid}" - The thread ID. *
  8. "{exception}" - The localized exception message. *
* @param dateFormat * The {@link SimpleDateFormat} format to use for dates. e.g. "yyyy.MM.dd hh:mm:ss". * @param useStackTraceHashes * If true, only print unique stack traces once and then refer to them by a simple 8 character hash * identifier. */ public LogEntryFormatter(String format, String dateFormat, boolean useStackTraceHashes) { this.df = new SimpleDateFormat(dateFormat); if (useStackTraceHashes) hashes = new ConcurrentHashMap<>(); fieldIndexes = new HashMap<>(); format = format .replaceAll("\\{date\\}", "%1\\$s") .replaceAll("\\{class\\}", "%2\\$s") .replaceAll("\\{method\\}", "%3\\$s") .replaceAll("\\{logger\\}", "%4\\$s") .replaceAll("\\{level\\}", "%5\\$s") .replaceAll("\\{msg\\}", "%6\\$s") .replaceAll("\\{threadid\\}", "%7\\$s") .replaceAll("\\{exception\\}", "%8\\$s"); this.format = format; // Construct a regular expression to match this log entry. int index = 1; StringBuilder re = new StringBuilder(); int S1 = 1; // Looking for % int S2 = 2; // Found %, looking for number. int S3 = 3; // Found number, looking for $. int S4 = 4; // Found $, looking for s. int state = 1; int i1 = 0; for (int i = 0; i < format.length(); i++) { char c = format.charAt(i); if (state == S1) { if (c == '%') state = S2; else { if (! (Character.isLetterOrDigit(c) || Character.isWhitespace(c))) re.append('\\'); re.append(c); } } else if (state == S2) { if (Character.isDigit(c)) { i1 = i; state = S3; } else { re.append("\\%").append(c); state = S1; } } else if (state == S3) { if (c == '$') { state = S4; } else { re.append("\\%").append(format.substring(i1, i)); state = S1; } } else if (state == S4) { if (c == 's') { int group = Integer.parseInt(format.substring(i1, i-1)); switch (group) { case 1: fieldIndexes.put("date", index++); re.append("(" + dateFormat.replaceAll("[mHhsSdMy]", "\\\\d").replaceAll("\\.", "\\\\.") + ")"); break; case 2: fieldIndexes.put("class", index++); re.append("([\\p{javaJavaIdentifierPart}\\.]+)"); break; case 3: fieldIndexes.put("method", index++); re.append("([\\p{javaJavaIdentifierPart}\\.]+)"); break; case 4: fieldIndexes.put("logger", index++); re.append("([\\w\\d\\.\\_]+)"); break; case 5: fieldIndexes.put("level", index++); re.append("(SEVERE|WARNING|INFO|CONFIG|FINE|FINER|FINEST)"); break; case 6: fieldIndexes.put("msg", index++); re.append("(.*)"); break; case 7: fieldIndexes.put("threadid", index++); re.append("(\\\\d+)"); break; case 8: fieldIndexes.put("exception", index++); re.append("(.*)"); break; default: // Fall through. } } else { re.append("\\%").append(format.substring(i1, i)); } state = S1; } } // The log parser String sre = re.toString(); if (sre.endsWith("\\%n")) sre = sre.substring(0, sre.length()-3); // Replace instances of %n. sre = sre.replaceAll("\\\\%n", "\\\\n"); rePattern = Pattern.compile(sre); fieldIndexes = mapFrom(fieldIndexes); } /** * Returns the regular expression pattern used for matching log entries. * * @return The regular expression pattern used for matching log entries. */ public Pattern getLogEntryPattern() { return rePattern; } /** * Returns the {@link DateFormat} used for matching dates. * * @return The {@link DateFormat} used for matching dates. */ public DateFormat getDateFormat() { return df; } /** * Given a matcher that has matched the pattern specified by {@link #getLogEntryPattern()}, returns the field value * from the match. * * @param fieldName * The field name. * Possible values are: *
    *
  • "date" *
  • "class" *
  • "method" *
  • "logger" *
  • "level" *
  • "msg" *
  • "threadid" *
  • "exception" *
* @param m The matcher. * @return The field value, or null if the specified field does not exist. */ public String getField(String fieldName, Matcher m) { Integer i = fieldIndexes.get(fieldName); return (i == null ? null : m.group(i)); } @Override /* Formatter */ public String format(LogRecord r) { String msg = formatMessage(r); Throwable t = r.getThrown(); String hash = null; int c = 0; if (hashes != null && t != null) { hash = hashCode(t); hashes.putIfAbsent(hash, new AtomicInteger(0)); c = hashes.get(hash).incrementAndGet(); if (c == 1) { msg = '[' + hash + '.' + c + "] " + msg; } else { msg = '[' + hash + '.' + c + "] " + msg + ", " + t.getLocalizedMessage(); t = null; } } String s = String.format(format, df.format(new Date(r.getMillis())), r.getSourceClassName(), r.getSourceMethodName(), r.getLoggerName(), r.getLevel(), msg, r.getThreadID(), r.getThrown() == null ? "" : r.getThrown().getMessage()); if (t != null) s += String.format("%n%s", ThrowableUtils.getStackTrace(r.getThrown())); return s; } private static String hashCode(Throwable t) { int i = 0; while (t != null) { for (StackTraceElement e : t.getStackTrace()) i ^= e.hashCode(); t = t.getCause(); } return Integer.toHexString(i); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy