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);
   }
}