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