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

de.bund.bva.isyfact.logging.layout.IsyJsonLayout Maven / Gradle / Ivy

The newest version!
package de.bund.bva.isyfact.logging.layout;

/*
 * #%L
 * isy-logging
 * %%
 * 
 * %%
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 * The Federal Office of Administration (Bundesverwaltungsamt, BVA)
 * 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.
 * #L%
 */

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Marker;

import de.bund.bva.isyfact.logging.IsyMarker;
import de.bund.bva.isyfact.logging.exceptions.FehlerhafterLogeintrag;
import de.bund.bva.isyfact.logging.exceptions.LoggingTechnicalRuntimeException;
import de.bund.bva.isyfact.logging.impl.Ereignisschluessel;
import de.bund.bva.isyfact.logging.impl.FachdatenMarker;
import de.bund.bva.isyfact.logging.impl.FehlerSchluessel;
import de.bund.bva.isyfact.logging.util.LoggingKonstanten;
import de.bund.bva.isyfact.logging.util.MdcHelper;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.contrib.json.JsonFormatter;
import ch.qos.logback.contrib.json.classic.JsonLayout;
import ch.qos.logback.core.CoreConstants;

/**
 * Logback layout for formatting log entries as JSON.
 * 

* In particular, the layout includes the passed markers. */ public class IsyJsonLayout extends JsonLayout { /** Constant for an empty correlation ID. */ private static final String LEERE_KORRELATIONSID = "none"; /** Attribute name of the time stamp. */ private static final String ZEITSTEMPEL_ATTR_NAME = "zeitstempel"; /** Attribute name of the parameters of a log message. */ private static final String PARAMETER_ATTR_NAME = "parameter"; /** Attribute name of the log message. */ private static final String NACHRICHT_ATTR_NAME = "nachricht"; /** Attribute name of the exception. */ private static final String EXCEPTION_ATTR_NAME = "exception"; /** Attribute name of the correlation ID. */ private static final String KORRELATIONSID_ATTR_NAME = "korrelationsid"; /** Attribute name for general markers. */ private static final String MARKER_ATTR_NAME = "marker"; /** Attribute name for shortened log messages. */ private static final String GEKUERZT_ATTR_NAME = "gekuerzt"; /** Maximum size of a log entry in bytes. */ private int maxLength = 32000; /** * Class constructor. */ public IsyJsonLayout() { super(); includeLevel = true; includeThreadName = true; // MDC is not output because we include it as 'korrelationsid' includeMDC = false; includeLoggerName = true; // Message is recorded neither raw nor formatted. We output the formatted message as a 'message'. includeFormattedMessage = false; includeMessage = false; includeException = true; includeContextName = true; // Timestamp is output manually as 'zeitstempel' includeTimestamp = false; appendLineSeparator = false; } @Override public String doLayout(ILoggingEvent event) { Map map = toJsonMap(event); if (map == null || map.isEmpty()) { return ""; } String result = getStringFromFormatter(map); if (result == null || result.isEmpty()) { return ""; } result = pruefeGroesse(map, result, event); return isAppendLineSeparator() ? result + CoreConstants.LINE_SEPARATOR : result; } private String getStringFromFormatter(Map map) { JsonFormatter formatter = getJsonFormatter(); if (formatter == null) { Exception fehler = new LoggingTechnicalRuntimeException(FehlerSchluessel.FEHLENDE_KONFIGURATION_JSON_LAYOUT, getClass().getName()); addError(fehler.getMessage(), fehler); return ""; } try { return formatter.toJsonString(map); } catch (Exception e) { Map stringMap = new LinkedHashMap<>(); for (Map.Entry entry : map.entrySet()) { if (MDC_ATTR_NAME.equals(entry.getKey())) { stringMap.put(entry.getKey(), entry.getValue()); } else { stringMap.put(entry.getKey(), entry.getValue().toString()); } } try { return formatter.toJsonString(stringMap); } catch (Exception ex) { Exception fehler = new FehlerhafterLogeintrag(FehlerSchluessel.FEHLER_SERIALISIERUNG_AUFRUFPARAMETER, ex); addError(fehler.getMessage(), fehler); return ""; } } } /** * {@inheritDoc} * * @see ch.qos.logback.contrib.json.classic.JsonLayout#toJsonMap(ch.qos.logback.classic.spi.ILoggingEvent) */ @Override protected Map toJsonMap(ILoggingEvent event) { // Creates a map of JSON attributes. Only the slf4j standard attributes are filled in the super class. // In particular, no markers are evaluated. Map jsonMap = new LinkedHashMap<>(); // The attributes are sorted in the log in the order in which they were added. String zeitstempel = formatTimestamp(event.getTimeStamp()); if (zeitstempel != null) { jsonMap.put(ZEITSTEMPEL_ATTR_NAME, zeitstempel); } @SuppressWarnings("unchecked") Map defaultMap = super.toJsonMap(event); jsonMap.putAll(defaultMap); // Include the message String msg = event.getFormattedMessage(); if (msg != null) { jsonMap.put(NACHRICHT_ATTR_NAME, msg); } // Include the correlation ID String korrelationsId = MdcHelper.liesKorrelationsId(); if (korrelationsId == null) { korrelationsId = LEERE_KORRELATIONSID; } jsonMap.put(KORRELATIONSID_ATTR_NAME, korrelationsId); // Evaluate the markers Marker marker = event.getMarker(); // Process the markers recursively. IsyFact markers are transferred directly to the jsonMap. List standardMarker = new ArrayList<>(); processMarker(marker, jsonMap, standardMarker); // Include the parameters of the message (placeholders) as separate attributes. Object[] parameter = event.getArgumentArray(); if (parameter != null) { for (int i = 0; i < parameter.length; i++) { jsonMap.put(PARAMETER_ATTR_NAME + (i + 1), parameter[i]); } } // Include standard markers as a single attribute. if (!standardMarker.isEmpty()) { jsonMap.put(MARKER_ATTR_NAME, standardMarker); } // Technical data in MDC: This allows the value of the "Fachdaten" marker to be overwritten again. boolean enthaeltFachlicheDaten = MdcHelper.liesMarkerFachdaten(); if (enthaeltFachlicheDaten) { // This overwrites the previous data type of the log entry! processMarker(new FachdatenMarker(), jsonMap, standardMarker); } return jsonMap; } /** * This method processes the passed marker and recursively iterates through its references. *

* IsyFact markers are transferred to the {@code jsonMap} as name / value pairs. All other "StandardMarkers" are * collected in the {@code standardMarker} list. * * @param marker * the marker to process * @param jsonMap * map to fill with IsyFact marker values by name * @param standardMarker * list to fill with the standard markers */ private void processMarker(Marker marker, Map jsonMap, List standardMarker) { if (marker == null) { return; } if (marker instanceof IsyMarker) { IsyMarker isyMarker = (IsyMarker) marker; // Values of root markers are not included if (!isyMarker.isRootMarker()) { // Markers with "NULL values" are also included. jsonMap.put(isyMarker.getName(), isyMarker.getValue()); } } else { standardMarker.add(marker.getName()); } Iterator iterator = marker.iterator(); while (iterator.hasNext()) { processMarker(iterator.next(), jsonMap, standardMarker); } } /** * This method checks whether the transferred string of the log entry exceeds the maximum size and must be * shortened. *

* If the log entry is too large, the log entry is shortened as follows: *

    *
  1. Remove all parameters from the log entry
  2. *
  3. Shorten the Exception field, if available
  4. *
  5. Shorten the Message field
  6. *
*

* If the log entry is still too long after it has been shortened, an exception is thrown. * * @param map * the map with the raw data of the log event * @param logeintrag * the map formatted as a string, in order to check the length of the log entry * @param event * the log event * @return the checked and possibly shortened log entry as a string */ private String pruefeGroesse(Map map, String logeintrag, ILoggingEvent event) { if (maxLength > 0 && // Check whether a maximum length has been defined (0 = any length) event.getLevel().isGreaterOrEqual(Level.INFO) && // Consider only log messages that have level INFO or higher logeintrag.length() >= (maxLength / 2.0)) { // Check whether the log message can reach the maximum size in bytes at all with its length byte[] zeichen = logeintrag.getBytes(StandardCharsets.UTF_8); int tatsaechlicheLaenge = zeichen.length; if (tatsaechlicheLaenge > maxLength) { // First remove all parameters from the log entry map.replaceAll((k, v) -> { if (k.startsWith(PARAMETER_ATTR_NAME)) { return Ereignisschluessel.DEBUG_LOG_GEKUERZT.getNachricht(); } else { return v; } }); map.put(GEKUERZT_ATTR_NAME, LoggingKonstanten.TRUE); int ueberhang = berechneUeberhang(map); if (ueberhang > 0) { if (map.containsKey(EXCEPTION_ATTR_NAME)) { // First shorten exception if present, then message if still too long ueberhang = feldKuerzen(EXCEPTION_ATTR_NAME, map, ueberhang); if (ueberhang > 0) { feldKuerzen(NACHRICHT_ATTR_NAME, map, ueberhang); } } else { // Otherwise directly shorten the message feldKuerzen(NACHRICHT_ATTR_NAME, map, ueberhang); } } return getStringFromFormatter(map); } } return logeintrag; } /** * This method truncates the contents of a field of a map based on the passed overhang, calculates the new overhang * after truncation, and returns it. * * @param schluessel * the key of the field in the map to be shortened * @param map * the map with the raw data of the log event * @param ueberhang * the overhang as number of characters * @return the updated overhang after the the field was truncated */ private int feldKuerzen(String schluessel, Map map, int ueberhang) { int neuerUeberhang = ueberhang; int vorherigerUeberhang; if (map.containsKey(schluessel)) { int feldlaenge; do { vorherigerUeberhang = neuerUeberhang; feldlaenge = map.get(schluessel).toString().length(); if (neuerUeberhang >= feldlaenge) { // the field can't be truncated and is replaced with a message indicating this map.put(schluessel, Ereignisschluessel.DEBUG_LOG_GEKUERZT.getNachricht()); } else { map.put(schluessel, map.get(schluessel).toString().substring(0, feldlaenge - neuerUeberhang - 1)); } neuerUeberhang = berechneUeberhang(map); } while (neuerUeberhang > 0 && !map.get(schluessel).toString() .equals(Ereignisschluessel.DEBUG_LOG_GEKUERZT.getNachricht()) && neuerUeberhang != vorherigerUeberhang); } return neuerUeberhang; } /** * This method calculates the overhang (number of characters that the log entry is longer than {@link #maxLength}) * of the log entry that is passed as a map. *

* The overhang is first calculated in bytes and then converted into a number of characters. * * @param map * the map with the raw data of the log event * @return the calculated overhang */ private int berechneUeberhang(Map map) { String logeintrag = getStringFromFormatter(map); int tatsaechlicheLaenge = logeintrag.getBytes(StandardCharsets.UTF_8).length; // Determines the number of characters to be cut off // Calculates bytes per character float byteZeichen = (float) tatsaechlicheLaenge / (float) logeintrag.length(); // Overhang indicates the number of characters to be removed return (int) ((tatsaechlicheLaenge - maxLength) / byteZeichen) + 1; } public int getMaxLength() { return maxLength; } public void setMaxLength(int maxLength) { this.maxLength = maxLength; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy