CentralLoggerClient.java
/*
** Module : CentralLoggerClient.java
** Abstract : CentralLogger concrete version for clients.
**
** Copyright (c) 2023-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- ----------------------------Description-----------------------------
** 001 GBB 20230207 Initial setup
** 002 GBB 20230519 Support for %as placeholder in log file names with serverSide.
** 003 GBB 20230608 Reuse Utils method pollAll.
** 004 GBB 20230619 Adding field level to each logger instance to boost isLoggable performance.
** 005 GBB 20230718 Expose server log service with a getter method.
** 006 GBB 20240729 Method getLeftoverLogs to directly return List instead of Supplier.
** Method publish to accept ContextLogRecord.
*/
/*
** 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.p2j.util.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.*;
import java.util.logging.*;
import java.util.stream.*;
/**
* A concrete version of CentralLogger for apps launched by
* {@link com.goldencode.p2j.main.ClientDriver#main} and sending logs to the server.
*/
public class CentralLoggerClient
extends CentralLogger
{
/** Thread-safe queue to store logs before being sent to the server on state sync or logger finish. */
private static final Queue<CentralLogRecord> LOG_BUFFER = new ConcurrentLinkedQueue<>();
/**
* The remote log service on the server to receive the leftover log messages on
* {@link CentralLoggerClient#finish}. Not used for other logs, otherwise causes infinite recursion.
*/
private static volatile CentralLogService logService;
/** This client process ID. */
private static volatile long clientPid;
/** This client process OS user ID. */
private static volatile String clientUserOs;
/** The server session ID. */
private static volatile Integer sessionId;
/** The server user ID. */
private static volatile String userId;
/**
* Package-private constructor for CentralLoggerClient.
*
* @param loggerName
* The name of the logger.
* @param level
* The level of the logger.
* @param checkParentLevels
* Flag to prevent recursion for class loaders.
* @param excludeSMContext
* Flag to prevent deadlock on SecurityManager.
*/
CentralLoggerClient(String loggerName, Level level, boolean checkParentLevels, boolean excludeSMContext)
{
super(loggerName, level, checkParentLevels, excludeSMContext);
}
/**
* Static setter for {@link CentralLoggerClient#logService}.
*
* @param logService
* The log service.
*/
public static void setLogService(CentralLogService logService)
{
CentralLoggerClient.logService = logService;
CentralLoggerClient.sessionId = logService.getSessionId();
CentralLoggerClient.userId = logService.getUserId();
}
/**
* Returns the server log service.
*
* @return The server log service.
*/
public static CentralLogService getLogService()
{
return logService;
}
/**
* Sets this client process ID.
*
* @param clientPid
* This client process ID.
*/
public static void setPid(long clientPid)
{
CentralLoggerClient.clientPid = clientPid;
}
/**
* Sets this client process OS user ID.
*
* @param clientUserOs
* This client process OS user ID.
*/
public static void setUserOs(String clientUserOs)
{
CentralLoggerClient.clientUserOs = clientUserOs;
}
/**
* Returns the remote server session ID.
*
* @return The remote server session ID.
*/
public static Integer getSessionId()
{
return sessionId;
}
/**
* Returns the remote server user ID.
*
* @return The remote server user ID.
*/
public static String getUserId()
{
return userId;
}
/**
* Allows graceful completion of logger work. Called right before server session is terminated or on
* exception in client execution. If {@link CentralLoggerClient#logService} has not been set, it leaves
* it to {@link CentralLogger#handleShutDown}, which is usually called later. Otherwise, the leftover
* logs, still not synced to the server, are pushed over rpc and a flag is raised to disable further
* logging, thus avoiding recursion.
*/
public static void finish()
{
if (getMode() != Mode.CLIENT || IS_LOGGER_WORK_FINISHED.get() || logService == null)
{
return;
}
CentralLogRecord[] leftoverLogs = pullLogs();
try
{
if (leftoverLogs.length > 0)
{
// rpc doesn't work with param of type CentralLogRecord[] (method not found)
// so iterating over the array.
for (CentralLogRecord log : leftoverLogs)
{
logService.publish(log);
}
}
IS_LOGGER_WORK_FINISHED.set(true);
logService.finalizeLogging(clientPid);
}
catch (Throwable t)
{
// CentralLogger generates a crash log file in the client launch dir if any logs present
pushLogs(leftoverLogs);
t.printStackTrace();
}
}
/**
* Copies all logs currently in the buffer to an array and returns it.
*
* @return CentralLogRecord array with all logs still not sent to the server.
*/
public static CentralLogRecord[] pullLogs()
{
return Utils.pollAll(LOG_BUFFER)
.orElse(Collections.emptyList())
.toArray(new CentralLogRecord[0]);
}
/**
* Returns a List of all logs present in the buffer converted to CentralLogRecord.
*
* @return A List of all logs present in the log buffer.
*/
static List<LogRecord> getLeftoverLogs()
{
return LOG_BUFFER.stream()
.map(CentralLogRecord::toLogRecord)
.collect(Collectors.toList());
}
/**
* Adds log records to the buffer.
*
* @param logRecords
* An array of log records.
*/
private static void pushLogs(CentralLogRecord[] logRecords)
{
LOG_BUFFER.addAll(Arrays.asList(logRecords));
}
/**
* Creates and configures a file handler for the specified client process.
*
* @param filePath
* The path to the log file.
* @param fileLimit
* The file limit, or the max bytes in a log file before rotation.
* @param fileCount
* The file count, or the max number of files before file overwrite starts.
* @param appServerName
* The appServer name.
*/
public static void setupFileHandler(String filePath, int fileLimit, int fileCount, String appServerName)
{
logService.setupFileHandler(filePath, fileLimit, fileCount, clientPid, clientUserOs, appServerName);
}
/**
* Publishes a log record. Before initialization the log record is added to the pre-init buffer. After
* initialization the log record is added to the regular buffer.
*
* @param logRecord
* A log record.
*/
@Override
protected void publish(ContextLogRecord logRecord)
{
if (getRootLevel() == null)
{
addToPreInitBuffer(logRecord);
return;
}
CentralLogRecord centralLogRecord = CentralLogRecord.from(logRecord, clientPid, clientUserOs);
LOG_BUFFER.add(centralLogRecord);
}
}