/* * Copyright 2015-2017 JKOOL, LLC. * * Licensed 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 com.jkoolcloud.tnt4j.stream.jmx.format; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.lang3.StringUtils; import com.jkoolcloud.tnt4j.core.*; import com.jkoolcloud.tnt4j.format.DefaultFormatter; import com.jkoolcloud.tnt4j.source.Source; import com.jkoolcloud.tnt4j.stream.jmx.scheduler.SchedulerImpl; import com.jkoolcloud.tnt4j.tracker.TrackingActivity; import com.jkoolcloud.tnt4j.tracker.TrackingEvent; import com.jkoolcloud.tnt4j.utils.Utils; /** * This class provides key/value formatting for tnt4j activities, events and snapshots. The output format follows the * following format: * <p> * {@code "OBJ:name-value-prefix,name1=value1,....,nameN=valueN"}. * </p> * Newline is added at the end of each line. * * @version $Revision: 1 $ * * @see SchedulerImpl */ public class FactNameValueFormatter extends DefaultFormatter { public static final String LF = "\n"; public static final String CR = "\r"; public static final String FIELD_SEP = ","; public static final String END_SEP = LF; public static final String PATH_DELIM = "\\"; public static final String EQ = "="; public static final String FS_REP = "!"; // NOTE: group 1 - original, group 2 - replacement // protected static final Pattern REP_CFG_PATTERN = Pattern.compile("\"(\\s*[^\"]+\\s*)\"->\"(\\s*[^\"]+\\s*)\""); // NOTE: group 1 - original, group 3 - replacement. Properly handles escaped quotes within quotes. protected static final Pattern REP_CFG_PATTERN = Pattern .compile("\"(\\s*([^\"\\\\]|\\\\.)+\\s*)\"->\"(\\s*([^\"\\\\]|\\\\.)+\\s*)\""); protected boolean serializeSimpleTypesOnly = false; protected Map<String, String> keyReplacements = new HashMap<>(); protected Map<String, String> valueReplacements = new HashMap<>(); public FactNameValueFormatter() { super("time.stamp={2},level={1},source={3},msg=\"{0}\""); } @Override public String format(TrackingEvent event) { StringBuilder nvString = new StringBuilder(1024); nvString.append("OBJ:Streams"); toString(nvString, event.getSource()).append(event.getOperation().getName()).append("\\Events").append(FIELD_SEP); Snapshot selfSnapshot = new PropertySnapshot("Self"); if (event.getCorrelator() != null) { Set<String> cids = event.getCorrelator(); if (!cids.isEmpty()) { selfSnapshot.add("corrid", cids); } } if (event.getTag() != null) { Set<String> tags = event.getTag(); if (!tags.isEmpty()) { selfSnapshot.add("tag", tags); } } if (event.getOperation().getUser() != null) { selfSnapshot.add("user", event.getOperation().getUser()); } if (event.getLocation() != null) { selfSnapshot.add("location", event.getLocation()); } selfSnapshot.add("level", event.getOperation().getSeverity()); selfSnapshot.add("pid", event.getOperation().getPID()); selfSnapshot.add("tid", event.getOperation().getTID()); selfSnapshot.add("elapsed.usec", event.getOperation().getElapsedTimeUsec()); event.getOperation().addSnapshot(selfSnapshot); Collection<Snapshot> sList = getSnapshots(event.getOperation()); for (Snapshot snap : sList) { toString(nvString, snap); } return nvString.append(END_SEP).toString(); } /** * Returns operation contained snapshots collection. * * @param op operation instance * @return collection of operation snapshots */ protected Collection<Snapshot> getSnapshots(Operation op) { return op.getSnapshots(); } @Override public String format(TrackingActivity activity) { StringBuilder nvString = new StringBuilder(1024); nvString.append("OBJ:Streams"); toString(nvString, activity.getSource()).append("\\Activities").append(FIELD_SEP); Snapshot selfSnapshot = new PropertySnapshot("Self"); if (activity.getCorrelator() != null) { Set<String> cids = activity.getCorrelator(); if (!cids.isEmpty()) { selfSnapshot.add("corrid", cids); } } if (activity.getUser() != null) { selfSnapshot.add("user", activity.getUser()); } if (activity.getLocation() != null) { selfSnapshot.add("location", activity.getLocation()); } selfSnapshot.add("level", activity.getSeverity()); selfSnapshot.add("id.count", activity.getIdCount()); selfSnapshot.add("pid", activity.getPID()); selfSnapshot.add("tid", activity.getTID()); selfSnapshot.add("snap.count", activity.getSnapshotCount()); selfSnapshot.add("elapsed.usec", activity.getElapsedTimeUsec()); activity.addSnapshot(selfSnapshot); Collection<Snapshot> sList = getSnapshots(activity); for (Snapshot snap : sList) { toString(nvString, snap); } return nvString.append(END_SEP).toString(); } @Override public String format(Snapshot snapshot) { StringBuilder nvString = new StringBuilder(1024); String prefix = "OBJ:Metrics\\" + snapshot.getCategory(); nvString.append(prefix).append(FIELD_SEP); toString(nvString, snapshot).append(END_SEP); return nvString.toString(); } @Override public String format(long ttl, Source source, OpLevel level, String msg, Object... args) { StringBuilder nvString = new StringBuilder(1024); nvString.append("OBJ:Streams"); toString(nvString, source).append("\\Message").append(FIELD_SEP); nvString.append("Self\\level=").append(getValueStr(level)).append(FIELD_SEP); nvString.append("Self\\msg-text=").append(Utils.quote(Utils.format(msg, args))).append(END_SEP); return nvString.toString(); } /** * Makes string representation of source and appends it to provided string builder. * * @param nvString string builder instance to append * @param source source instance to represent as string * @return appended string builder reference */ protected StringBuilder toString(StringBuilder nvString, Source source) { Source parent = source.getSource(); if (parent != null) { toString(nvString, parent); } nvString.append(PATH_DELIM).append(getSourceNameStr(source.getName())); return nvString; } /** * Makes decorated string representation of source name. * <p> * Replaces "{@value #FIELD_SEP}" to "{@value #FS_REP}". * * @param sourceName source name * @return decorated string representation of source name */ protected String getSourceNameStr(String sourceName) { return sourceName.replace(FIELD_SEP, FS_REP); } /** * Returns snapshot contained properties collection. * * @param snap snapshot instance * @return collection of snapshot properties */ protected Collection<Property> getProperties(Snapshot snap) { return snap.getSnapshot(); } /** * Makes string representation of snapshot and appends it to provided string builder. * * @param nvString string builder instance to append * @param snap snapshot instance to represent as string * @return appended string builder reference */ protected StringBuilder toString(StringBuilder nvString, Snapshot snap) { Collection<Property> list = getProperties(snap); String sName = getSnapNameStr(snap.getName()); for (Property p : list) { Object value = p.getValue(); nvString.append(getKeyStr(sName, p.getKey())); nvString.append(EQ).append(getValueStr(value)).append(FIELD_SEP); } return nvString; } /** * Determine if a given value can be meaningfully serialized to string. * * @param value value to test for serialization * @return {@code true} if a given value can be serialized to string meaningfully, {@code false} - otherwise */ protected static boolean isSerializable(Object value) { return value == null || value.getClass().isPrimitive() || value.getClass().isEnum() || value instanceof String || value instanceof Number || value instanceof Boolean || value instanceof Character; } /** * Makes decorated string representation of snapshot name. * <p> * Replaces "{@value #EQ}" to "{@value #PATH_DELIM}" and "{@value #FIELD_SEP}" to "{@value #FS_REP}". * * @param snapName snapshot name * @return decorated string representation of snapshot name */ protected String getSnapNameStr(String snapName) { return snapName.replace(EQ, PATH_DELIM).replace(FIELD_SEP, FS_REP); } /** * Makes decorated string representation of argument attribute key. * <p> * Key representation string gets symbols replaced using ones defined in {@link #keyReplacements} map. * * @param sName snapshot name * @param pKey property key * @return decorated string representation of attribute key * * @see #initDefaultKeyReplacements() */ protected String getKeyStr(String sName, String pKey) { String keyStr = sName + PATH_DELIM + pKey; for (Map.Entry<String, String> kre : keyReplacements.entrySet()) { keyStr = keyStr.replace(kre.getKey(), kre.getValue()); } return keyStr; } /** * Makes decorated string representation of argument attribute value. * <p> * If property {@link #serializeSimpleTypesOnly} is set to {@code true} - validates if value can be represented as * simple type. If no, then actual value is replaced by dummy string {@code "<unsupported value type>"}. * <p> * Value representation string containing {@code "\n"} or {@code "\r"} symbols gets those replaced by escaped * representations {@code "\\n"} amd {@code "\\r"}. * <p> * Value representation string gets symbols replaced using ones defined in {@link #valueReplacements} map. * * @param value attribute value * @return decorated string representation of attribute value * * @see #toString(Object) * @see #initDefaultValueReplacements() */ protected String getValueStr(Object value) { String valStr; if (serializeSimpleTypesOnly && !isSerializable(value)) { valStr = value == null ? "<null>" : "<unsupported value type>";// : " + value.getClass() + ">"; // System.out.println("Unsupported value type=" + (value == null ? null : value.getClass()) + " value=" // + toString(value)); } else { valStr = toString(value); } valStr = valStr.replace(LF, "\\n").replace(CR, "\\r"); for (Map.Entry<String, String> vre : valueReplacements.entrySet()) { valStr = valStr.replace(vre.getKey(), vre.getValue()); } return valStr; } /** * Makes string representation of argument attribute value. * * @param value attribute value * @return string representation of attribute value */ protected String toString(Object value) { if (value instanceof Collection) { Collection<?> c = (Collection<?>) value; Object[] dca = c.toArray(); value = dca[0]; } return String.valueOf(value); } @Override public void setConfiguration(Map<String, Object> settings) { super.setConfiguration(settings); serializeSimpleTypesOnly = Utils.getBoolean("SerializeSimplesOnly", settings, serializeSimpleTypesOnly); String kReplacements = Utils.getString("KeyReplacements", settings, ""); if (StringUtils.isEmpty(kReplacements)) { initDefaultKeyReplacements(); } else { Matcher m = REP_CFG_PATTERN.matcher(kReplacements); while (m.find()) { keyReplacements.put(m.group(1), m.group(3)); } } String vReplacements = Utils.getString("ValueReplacements", settings, ""); if (StringUtils.isEmpty(vReplacements)) { initDefaultValueReplacements(); } else { Matcher m = REP_CFG_PATTERN.matcher(vReplacements); while (m.find()) { valueReplacements.put(m.group(1), m.group(3)); } } } /** * Initializes default set symbol replacements for a attribute keys. * * <p> * Default keys string replacements mapping is: * <ul> * <li>{@code " "} to {@code "_"}</li> * </ul> */ protected void initDefaultKeyReplacements() { keyReplacements.put(" ", "_"); } /** * Initializes default set symbol replacements for a attribute values. * <p> * Default value string replacements mapping is: * <ul> * <li>{@code ";"} to {@code "|"}</li> * <li>{@code ","} to {@code "|"}</li> * <li>{@code "["} to {@code "{("}</li> * <li>{@code "["} to {@code ")}"}</li> * </ul> */ protected void initDefaultValueReplacements() { valueReplacements.put(";", "|"); valueReplacements.put(",", "|"); valueReplacements.put("[", "{("); valueReplacements.put("]", ")}"); } }