# CentralLogRecord.java

    /*
    ** Module   : CentralLogRecord.java
    ** Abstract : Central Log Record
    **
    ** Copyright (c) 2023-2024, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ----------------------------Description-----------------------------
    ** 001 GBB 20230207 Initial setup
    ** 002 GBB 20230613 convertToLevel moved to LoggingUtils.
    **     TJD 20240123 Java 17 compatibility updates
    ** 003 GBB 20240729 Create ContextLogRecord with LogContext.
    */
    /*
     ** This program is free software: you can redistribute it and/or modify
     ** it under the terms of the GNU Affero General 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 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 java.io.*;
    import java.util.*;
    import java.util.logging.*;

    import static com.goldencode.util.NativeTypeSerializer.*;

    /**
     * An alternative to {@link LogRecord} that implements the {@link Externalizable} interface. Used to
     * transfer log records between the client and the server.
     */
    public class CentralLogRecord
    implements Externalizable
    {
       /** Process ID. */
       private long pid;

       /** OS user ID. */
       private String userOS;

       /** The level of the log msg. */
       private Level level;

       /** The class where the msg originates from. */
       private String sourceClassName;

       /** The method where the msg originates from. */
       private String sourceMethodName;

       /** The log message. */
       private String message;

       /** The event time in milliseconds since 1970. */
       private long millis = new Date().getTime();

       /** The Throwable associated with log message. */
       private Throwable thrown;

       /** The name of the logger. */
       private String loggerName;

       /** The format parameters. */
       private Object[] parameters;

       /** The thread name. */
       private String threadName;

       /** The FWD account. */
       private String fwdUser;

       /** The session ID. */
       private String sessionId;

       /** The thread id. */
       private Integer threadId;

       /**
        * Converts a standard java {@link ContextLogRecord} to {@link CentralLogRecord}.
        *
        * @param    logRecord
        *           A log record.
        * @param    pid
        *           A process ID.
        * @param    userOS
        *           OS user ID.
        *
        * @return   The log record converted to CentralLogRecord.
        */
       public static CentralLogRecord from(ContextLogRecord logRecord, long pid, String userOS)
       {
          CentralLogRecord clr = new CentralLogRecord();
          clr.pid = pid;
          clr.userOS = userOS;
          clr.level = logRecord.getLevel();
          clr.sourceClassName = logRecord.getSourceClassName();
          clr.sourceMethodName = logRecord.getSourceMethodName();
          clr.millis = logRecord.getMillis();
          clr.loggerName = logRecord.getLoggerName();
          clr.message = logRecord.getMessage();
          clr.parameters = logRecord.getParameters();
          clr.thrown = logRecord.getThrown();
          clr.threadName = Thread.currentThread().getName();
          clr.threadId = logRecord.getThreadID();

          ContextLogRecord.LogContext logContext = logRecord.getLogContext();
          if (logContext != null)
          {
             clr.fwdUser = logContext.getFwdUser();
             clr.sessionId = logContext.getSessionId();
          }
          return clr;
       }

       /**
        * Writes the fields of this object to the out stream in a format supported by Externalizable. Uses
        * helper methods from {@link com.goldencode.util.NativeTypeSerializer}.
        *
        * @param    out
        *           The stream to write the object to.
        *
        * @throws   IOException Any I/O exceptions that may occur
        */
       @Override
       public void writeExternal(ObjectOutput out)
       throws IOException
       {
          writeLong(out, pid);
          writeString(out, userOS);
          writeInteger(out, level == null ? -1 : level.intValue());
          writeString(out, sourceClassName);
          writeString(out, sourceMethodName);

          if (parameters != null && parameters.length > 0)
          {
             // Params can be of any type, not Externalizable, so format the msg
             try
             {
                message = String.format(message, parameters);
             }
             catch (Throwable ignore)
             {
             }
             parameters = null;
          }

          if (thrown != null)
          {
             // Throwable is not Externalizable, so concat to msg
             StringBuilder sb = new StringBuilder(message);
             sb.append(" ");
             CentralLogFormatter.dumpThrowable(sb, thrown);
             message = sb.toString();
          }

          writeString(out, message);
          writeLong(out, millis);
          writeString(out, loggerName);
          writeString(out, threadName);
          writeInteger(out, threadId);
          writeString(out, fwdUser);
          writeString(out, sessionId);
       }

       /**
        * Restores the contents of this object from the input stream from a format supported by Externalizable.
        * Uses helper methods from {@link com.goldencode.util.NativeTypeSerializer}.
        *
        * @param    in
        *           The stream to read data from.
        *
        * @throws   IOException Any I/O exceptions that may occur
        * @throws   ClassNotFoundException The class for an object being restored cannot be found.
        */
       @Override
       public void readExternal(ObjectInput in)
       throws IOException,
              ClassNotFoundException
       {
          pid = readLong(in);
          userOS = readString(in);
          level = LoggingUtil.convertToLevel(readInteger(in)).orElse(null);
          sourceClassName = readString(in);
          sourceMethodName = readString(in);
          message = readString(in);
          millis = readLong(in);
          loggerName = readString(in);
          threadName = readString(in);
          threadId = readInteger(in);
          fwdUser = readString(in);
          sessionId = readString(in);
       }

       /**
        * Returns this object converted to {@link LogRecord}.
        *
        * @return   This object converted to LogRecord.
        */
       @SuppressWarnings("deprecation")
       public ContextLogRecord toLogRecord()
       {
          if (this.level == null)
          {
    	      // preventing NPE, no log level is not expected though
             this.level = Level.INFO;
          }
          ContextLogRecord cl = new ContextLogRecord(this.level, this.message);
          cl.setSourceClassName(this.sourceClassName);
          cl.setSourceMethodName(this.sourceMethodName);
    // LogRecord.setInstant is not available in JDK 1.8
    // In JDK 9+ cl.setMillis should be replaced with:
    // cl.setInstant(Instant.ofEpochMilli(this.millis));
    // next line has to stay for compatibility with JDK 1.8
          cl.setMillis(this.millis);
          cl.setLoggerName(this.loggerName);
          cl.setParameters(this.parameters);
          cl.setThrown(this.thrown);
          if (this.threadId != null)
          {
             cl.setThreadID(this.threadId);
          }

          ContextLogRecord.LogContext logContext = new ContextLogRecord.LogContext();
          logContext.setFwdUser(fwdUser);
          logContext.setSessionId(sessionId);
          logContext.setThreadName(threadName);
          logContext.setPid(pid);
          logContext.setThreadId(LoggingUtil.addLeadingZeros(threadId, 8));
          cl.setLogContext(logContext);

          return cl;
       }

       /**
        * Getter for the process ID.
        *
        * @return   The process ID.
        */
       public long getPid()
       {
          return pid;
       }

       /**
        * Getter for the process OS user ID.
        *
        * @return   The process OS user ID.
        */
       public String getUserOS()
       {
          return userOS;
       }

       /**
        * Getter for the log level.
        *
        * @return   The log level.
        */
       public Level getLevel()
       {
          return level;
       }

       /**
        * Setter for the log level.
        *
        * @param    level
        *           The log level.
        */
       public void setLevel(Level level)
       {
          this.level = level;
       }

       /**
        * Getter for the source class name.
        *
        * @return   The source class name.
        */
       public String getSourceClassName()
       {
          return sourceClassName;
       }

       /**
        * Setter for the source class name.
        *
        * @param    sourceClassName
        *           The source class name.
        */
       public void setSourceClassName(String sourceClassName)
       {
          this.sourceClassName = sourceClassName;
       }

       /**
        * Getter for the source method name.
        *
        * @return   The source method name.
        */
       public String getSourceMethodName()
       {
          return sourceMethodName;
       }

       /**
        * Setter for the source method name.
        *
        * @param    sourceMethodName
        *           The source method name.
        */
       public void setSourceMethodName(String sourceMethodName)
       {
          this.sourceMethodName = sourceMethodName;
       }

       /**
        * Getter for the message.
        *
        * @return   The message.
        */
       public String getMessage()
       {
          return message;
       }

       /**
        * Setter for the message.
        *
        * @param    message
        *           The message.
        */
       public void setMessage(String message)
       {
          this.message = message;
       }

       /**
        * Getter for the millis.
        *
        * @return   The millis.
        */
       public long getMillis()
       {
          return millis;
       }

       /**
        * Setter for the millis.
        *
        * @param    millis
        *           The millis.
        */
       public void setMillis(long millis)
       {
          this.millis = millis;
       }

       /**
        * Getter for the Throwable.
        *
        * @return   The Throwable.
        */
       public Throwable getThrown()
       {
          return thrown;
       }

       /**
        * Setter for the Throwable.
        *
        * @param    thrown
        *           The Throwable.
        */
       public void setThrown(Throwable thrown)
       {
          this.thrown = thrown;
       }

       /**
        * Getter for the logger name.
        *
        * @return   The logger name.
        */
       public String getLoggerName()
       {
          return loggerName;
       }

       /**
        * Setter for the logger name.
        *
        * @param    loggerName
        *           The logger name.
        */
       public void setLoggerName(String loggerName)
       {
          this.loggerName = loggerName;
       }

       /**
        * Getter for the parameters.
        *
        * @return   The parameters.
        */
       public Object[] getParameters()
       {
          return parameters;
       }

       /**
        * Setter for the parameters.
        *
        * @param    parameters
        *           The parameters.
        */
       public void setParameters(Object... parameters)
       {
          this.parameters = parameters;
       }

       /**
        * Getter for the thread id.
        *
        * @return   The thread id.
        */
       public Integer getThreadId()
       {
          return threadId;
       }

       /**
        * Getter for the thread name.
        *
        * @return   The thread name.
        */
       public String getThreadName()
       {
          return threadName;
       }

       /**
        * Getter for the FWD account.
        *
        * @return   The FWD account.
        */
       public String getFwdUser()
       {
          return fwdUser;
       }

       /**
        * Getter for the session ID.
        *
        * @return   The session ID.
        */
       public String getSessionId()
       {
          return sessionId;
       }
    }