LoggingUtil.java
/*
** Module : LoggingUtil.java
** Abstract : Logging Utilities
**
** Copyright (c) 2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- ----------------------------Description-----------------------------
** 001 GBB 20230424 Initial setup
** 002 GBB 20230519 Support for disabling rotation.
** 003 GBB 20230530 Remove ConfigurationException from canWriteToPath.
** More condensed format for SORTABLE_DATETIME_FORMAT.
** 004 GBB 20230602 Add a convenience method to format current datetime.
** 005 RAA 20230608 Added a resolveLogFile function that includes the database name.
** 006 GBB 20230613 Added support for vars in square brackets in log file name placeholders.
** convertToLevel methods moved to LoggingUtils.
** 007 GBB 20230623 Added methods to format numbers/texts with leading symbols/zeros.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.util.logging;
import com.goldencode.util.*;
import java.io.*;
import java.util.*;
import java.util.function.*;
import java.util.logging.*;
/** Logging utilities. */
public class LoggingUtil
{
/** Format for file names with sortable datetime to the seconds. java.util.Formatter specifiers. */
public static final String SORTABLE_DATETIME_FORMAT = "%tY%tm%td_%tH%tM%tS";
/** The placeholder for process id. */
private static final String PLACEHOLDER_PID = "%pid";
/** The characters at the start of a placeholder for var. */
private static final String VAR_PLACEHOLDER_START = "[";
/** The characters at the end of a placeholder for var. */
private static final String VAR_PLACEHOLDER_END = "]";
/** Logger. */
private static final CentralLogger LOG = CentralLogger.get(LoggingUtil.class);
/**
* Returns the resolved log file name.
*
* @param logFile
* File path for the log.
* @param processIdSupplier
* Supploer for the process Id.
* @param processType
* Client, server, whatever you like.
* @param isRotationDisabled
* Flag to indicate rotation has been disabled.
*
* @return The resolved log file name.
*/
public static String resolveLogFile(String logFile,
Supplier<Long> processIdSupplier,
String processType,
boolean isRotationDisabled)
{
return resolveLogFile(logFile,
processIdSupplier,
null,
null,
null,
processType,
null,
isRotationDisabled);
}
/**
* Returns the resolved log file name.
*
* @param logFile
* File path for the log.
* @param processIdSupplier
* Supploer for the process Id.
* @param processType
* Client, server, whatever you like.
* @param databaseName
* The name of the database in use.
* @param isRotationDisabled
* Flag to indicate rotation has been disabled.
*
* @return The resolved log file name.
*/
public static String resolveLogFile(String logFile,
Supplier<Long> processIdSupplier,
String processType,
String databaseName,
boolean isRotationDisabled)
{
return resolveLogFile(logFile,
processIdSupplier,
null,
null,
null,
processType,
databaseName,
isRotationDisabled);
}
/**
* Returns the resolved log file name.
*
* @param logFile
* File path for the log.
* @param processIdSupplier
* Supploer for the process Id.
* @param userOS
* Operating system user.
* @param userFwd
* FWD context user.
* @param appServerName
* AppServer name.
* @param mode
* Client, server, whatever you like.
* @param databaseName
* The name of the database in use.
* @param isRotationDisabled
* Flag to indicate rotation has been disabled.
*
* @return The resolved log file name.
*/
public static String resolveLogFile(String logFile,
Supplier<Long> processIdSupplier,
String userOS,
String userFwd,
String appServerName,
String mode,
String databaseName,
boolean isRotationDisabled)
{
if (!StringHelper.hasContent(logFile))
{
return null;
}
if (isRotationDisabled)
{
logFile = logFile.replace("_%g", "")
.replace("%g", "");
}
if (logFile.contains(PLACEHOLDER_PID))
{
logFile = logFile.replace(PLACEHOLDER_PID, String.valueOf(processIdSupplier.get()));
}
if (mode == null && logFile.contains("_%m"))
{
logFile = logFile.replace("_%m", "");
}
if (userFwd == null && logFile.contains("_%uf"))
{
logFile = logFile.replace("_%uf", "");
}
if (userOS == null && logFile.contains("_%uos"))
{
logFile = logFile.replace("_%uos", "");
}
if (appServerName == null && logFile.contains("_%as"))
{
logFile = logFile.replace("_%as", "");
}
if (databaseName == null && logFile.contains("_%db"))
{
logFile = logFile.replace("_%db", "");
}
logFile = logFile.replace("%m", mode != null ? mode : "")
.replace("%uf", userFwd != null ? userFwd : "")
.replace("%uos", userOS != null ? userOS : "")
.replace("%as", appServerName != null ? appServerName : "")
.replace("%db", databaseName != null ? databaseName : "");
if (logFile.contains(VAR_PLACEHOLDER_START) && logFile.contains(VAR_PLACEHOLDER_END))
{
String varName;
while ((varName = getFirstBetween(logFile, VAR_PLACEHOLDER_START, VAR_PLACEHOLDER_END)) != null)
{
String toReplace = VAR_PLACEHOLDER_START + varName + VAR_PLACEHOLDER_END;
String value = null;
// TODO: receive values for var names from bootstrap
logFile = logFile.replace(toReplace, value == null ? "" : value);
}
}
return replaceFormatterDateTime(logFile);
}
/**
* Format the current datetime.
*
* @return The formatted datetime.
*/
public static String formatNow()
{
return replaceFormatterDateTime(SORTABLE_DATETIME_FORMAT);
}
/**
* Return the formatted log file path.
*
* @param filePath
* File path for the log.
*
* @return The formatted log file path.
*/
public static String replaceFormatterDateTime(String filePath)
{
// non-datetime Formatter specifiers should not be handled at this stage, so escape them
filePath = filePath.replaceAll("%", "%%");
filePath = filePath.replaceAll("%%t", "%t");
int foundTimeSpecifiers = -1;
int indexOfTimeSpecifier = -1;
do
{
foundTimeSpecifiers++;
indexOfTimeSpecifier = filePath.indexOf("%t", indexOfTimeSpecifier + 1);
}
while (indexOfTimeSpecifier != -1);
Date[] dateArgs = new Date[foundTimeSpecifiers];
Arrays.fill(dateArgs, new Date());
filePath = String.format(filePath, (Object[]) dateArgs);
return filePath.replaceAll("%%", "%");
}
/**
* Return if the process can write to the file path.
*
* @param filePath
* File path for the log.
*
* @return <code>true</code> if the process can write to the file path, <code>false</code> otherwise.
*/
public static boolean canWriteToPath(String filePath)
{
try
{
File location = new File(filePath);
File dir = location.getParentFile();
// create the dirs to the file
if (dir != null && !dir.exists())
{
if (!dir.mkdirs())
{
LOG.warning("Cannot create dir for log path " + filePath);
return false;
}
}
if (location.exists() && (!location.isFile() || !location.canWrite()))
{
LOG.warning("The provided log path is not a file or is not writable: " + filePath);
return false;
}
if (!location.exists())
{
if (location.createNewFile())
{
if (!location.isFile() || !location.canWrite())
{
location.delete();
LOG.warning("The provided log path is not a file or is not writable: " + filePath);
return false;
}
location.delete();
}
else
{
LOG.warning("The provided log path can't be created: " + filePath);
return false;
}
}
}
catch (Exception ex)
{
LOG.warning("Exception in setting up the provided log file path " + filePath, ex);
}
return true;
}
/**
* Utility method to convert string to java.util.logging.Level. Not the same as
* {@link Level#parse(String)} that throws exceptions with invalid values.
*
* @param levelString
* The string representation of the level.
*
* @return Optional for the parsed java.util.logging.Level.
*/
public static Optional<Level> convertToLevel(String levelString)
{
return java.util.stream.Stream.of(Level.OFF, Level.SEVERE, Level.WARNING, Level.INFO,
Level.CONFIG, Level.FINE, Level.FINER, Level.FINEST,
Level.ALL)
.filter(level -> level.getName().equalsIgnoreCase(levelString))
.findAny();
}
/**
* Utility method to convert integer to java.util.logging.Level. Not the same as
* {@link Level#parse(String)} that throws exceptions with invalid values.
*
* @param levelInt
* The integer representation of the level.
*
* @return Optional for the parsed java.util.logging.Level.
*/
static Optional<Level> convertToLevel(Integer levelInt)
{
return java.util.stream.Stream.of(Level.OFF, Level.SEVERE, Level.WARNING, Level.INFO,
Level.CONFIG, Level.FINE, Level.FINER, Level.FINEST,
Level.ALL)
.filter(level -> level.intValue() == levelInt)
.findAny();
}
/**
* Returns the content between the starting and the ending string.
*
* @param text
* The text to search in.
* @param start
* The wrapper start. To be excluded.
* @param end
* The wrapper end. To be excluded.
*
* @return The content between the starting and the ending string.
*/
private static String getFirstBetween(String text, String start, String end)
{
int startIndex = text.indexOf(start);
int endIndex = text.indexOf(end);
if (startIndex == -1 || endIndex == -1)
{
return null;
}
return text.substring(startIndex + 1, endIndex);
}
/**
* Format a number by adding leading zeros up to the defined amount of characters.
*
* @param number
* The number to format.
* @param totalCharsCount
* File path for the log.
*
* @return The formatted number with leading zeros.
*/
public static String addLeadingZeros(Integer number, int totalCharsCount)
{
String numberAsString = String.valueOf(number == null ? 0 : number);
return addLeadingSymbols(numberAsString, totalCharsCount, '0');
}
/**
* Format a text by adding leading symbols up to the defined amount of characters.
*
* @param originalValue
* The value to format.
* @param totalCharsCount
* File path for the log.
* @param symbol
* The character to use for filling in leading spaces.
*
* @return The formatted text with leading symbols.
*/
public static String addLeadingSymbols(String originalValue, int totalCharsCount, char symbol)
{
int leadingSymbolsCount = totalCharsCount - originalValue.length();
if (leadingSymbolsCount <= 0)
{
return originalValue;
}
char[] leadingSymbols = new char[leadingSymbolsCount];
Arrays.fill(leadingSymbols, symbol);
return String.valueOf(leadingSymbols) + originalValue;
}
}