Slf4jLogRecord.java

/*
 * Copyright (c) 2001-2017, Zoltan Farkas All Rights Reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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 General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * Additionally licensed with:
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.spf4j.log;

import com.google.common.collect.Maps;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.slf4j.Marker;
import org.spf4j.base.StackSamples;
import org.spf4j.base.avro.Converters;
import static org.spf4j.base.avro.Converters.convert;
import org.spf4j.base.avro.LogRecord;
import org.spf4j.base.avro.StackSampleElement;
import org.spf4j.io.ObjectAppenderSupplier;

/**
 * @author Zoltan Farkas
 */
public interface Slf4jLogRecord {

  /**
   * @return all slf4j log arguments, getNrMessageArguments() message arguments followed by additional payload
   * like exceptions, or other data.
   */
  @SuppressFBWarnings(value = "EI_EXPOSE_REP")
  @Nonnull
  Object[] getArguments();

  /**
   * @return all extra arguments. (arguments that are not parameters for the message)
   */
  @Nonnull
  Object[] getExtraArgumentsRaw();

  /**
   * @return all non Throwable extra arguments.
   */
  @Nonnull
  Object[] getExtraArguments();

  /**
   * @return Throwable from extra arguments. If multiple, will return first will all others as suppressed.
   */
  @Nullable
  Throwable getExtraThrowable();

  Level getLevel();

  String getLoggerName();

  @Nullable
  Marker getMarker();

  @Nonnull
  String getMessage();

  String getMessageFormat();

  int getNrMessageArguments();

  String getThreadName();

  long getTimeStamp();

  default Instant getTimeStampInstant() {
    return Instant.ofEpochMilli(getTimeStamp());
  }

  /**
   * Indicates that this log record has been sent to the logging backend to persist.
   *
   * @return
   */
  boolean isLogged();

  void setIsLogged();

  void attach(Object obj);

  Set<Object> getAttachments();

  boolean hasAttachment(Object obj);

  static int compareByTimestamp(Slf4jLogRecord a, Slf4jLogRecord b) {
    long timeDiff = a.getTimeStamp() - b.getTimeStamp();
    if (timeDiff > 0) {
      return 1;
    } else if (timeDiff < 0) {
      return -1;
    } else {
      return 0;
    }
  }

  @SuppressFBWarnings("WOC_WRITE_ONLY_COLLECTION_LOCAL")
  default LogRecord toLogRecord(final String origin, final String ptraceId) {
    java.lang.Throwable extraThrowable = this.getExtraThrowable();
    Marker marker = this.getMarker();
    Object[] extraArguments = this.getExtraArguments();
    Map<String, Object> attribs = null;
    List<Object> xArgs;
    String traceId = ptraceId;
    List<StackSampleElement> profiles = Collections.EMPTY_LIST;
    if (extraArguments.length == 0) {
      xArgs = Collections.emptyList();
    } else {
      int nrAttribs = 0;
      for (Object obj : extraArguments) {
        if (obj instanceof LogAttribute) {
          LogAttribute la = (LogAttribute) obj;
          String name = la.getName();
          switch (name) {
            case LogAttribute.ID_ATTR_NAME:
              traceId = la.getValue().toString();
              break;
            case LogAttribute.PROFILE_SAMPLES_ATTR_NAME:
              profiles = Converters.convert((StackSamples) la.getValue());
              break;
            default:
              // nothiing to do.
          }
          nrAttribs++;
        }
      }
      if (nrAttribs == 0) {
        xArgs = Arrays.asList(extraArguments);
      } else {
        if (nrAttribs == extraArguments.length) {
          xArgs = Collections.EMPTY_LIST;
        } else {
          xArgs = new ArrayList<>(extraArguments.length - nrAttribs);
        }
        attribs = Maps.newHashMapWithExpectedSize(nrAttribs + (marker == null ? 0 : 1));
        for (Object obj : extraArguments) {
          if (obj instanceof LogAttribute) {
            LogAttribute la = (LogAttribute) obj;
            String aName = la.getName();
            switch (aName) {
              case LogAttribute.ID_ATTR_NAME:
              case LogAttribute.PROFILE_SAMPLES_ATTR_NAME:
                break;
              default:
                attribs.put(aName, la.getValue());
            }
          } else {
            xArgs.add(obj);
          }
        }
        if (marker != null) {
          attribs.put(marker.getName(), marker);
        }
      }
    }
    int nrMsgArgs = this.getNrMessageArguments();
    List<String> messageArgs;
    if (nrMsgArgs == 0) {
        messageArgs = Collections.emptyList();
    } else {
        Object[] args = this.getArguments();
        String[] ma = new String[nrMsgArgs];
        for (int i = 0; i < nrMsgArgs; i++) {
          Object arg = args[i];
          if (arg == null) {
            ma[i] = "null";
          } else {
            ma[i] = ObjectAppenderSupplier.TO_STRINGER.get(arg.getClass()).toString(arg);
          }
        }
        messageArgs = Arrays.asList(ma);
    }
    return new LogRecord(origin, traceId, this.getLevel().getAvroLevel(),
            Instant.ofEpochMilli(this.getTimeStamp()),
            this.getLoggerName(), this.getThreadName(), this.getMessageFormat(),
            messageArgs, xArgs,
            attribs == null ? Collections.emptyMap() : attribs,
            extraThrowable == null ? null : convert(extraThrowable), profiles);
  }

}