All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.solr.util.SolrLogLayout Maven / Gradle / Ivy

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.solr.util;

import java.nio.charset.Charset;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.layout.AbstractStringLayout;
import org.apache.solr.cloud.ZkController;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.StringUtils;
import org.apache.solr.common.cloud.DocCollection;
import org.apache.solr.common.cloud.Replica;
import org.apache.solr.common.util.SuppressForbidden;
import org.apache.solr.core.SolrCore;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.request.SolrRequestInfo;
import org.slf4j.MDC;

import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP;
import static org.apache.solr.common.cloud.ZkStateReader.CORE_NAME_PROP;
import static org.apache.solr.common.cloud.ZkStateReader.NODE_NAME_PROP;
import static org.apache.solr.common.cloud.ZkStateReader.REPLICA_PROP;
import static org.apache.solr.common.cloud.ZkStateReader.SHARD_ID_PROP;

@SuppressForbidden(reason = "class is specific to log4j2")
@Plugin(name = "SolrLogLayout", category = "Core", elementType = "layout", printObject = true)
public class SolrLogLayout extends AbstractStringLayout {

  protected SolrLogLayout(Charset charset) {
    super(charset);
  }

  @PluginFactory
  public static SolrLogLayout createLayout(@PluginAttribute(value = "charset", defaultString = "UTF-8") Charset charset) {
    return new SolrLogLayout(charset);
  }

  /**
   * Add this interface to a thread group and the string returned by getTag()
   * will appear in log statements of any threads under that group.
   */
  public static interface TG {
    public String getTag();
  }

  @SuppressForbidden(reason = "Need currentTimeMillis to compare against log event timestamp. " +
    "This is inaccurate but unavoidable due to interface limitations, in any case this is just for logging.")
  final long startTime = System.currentTimeMillis();

  long lastTime = startTime;
  Map methodAlias = new HashMap<>();
  
  public static class Method {
    public String className;
    public String methodName;
    
    public Method(String className, String methodName) {
      this.className = className;
      this.methodName = methodName;
    }
    
    @Override
    public int hashCode() {
      return className.hashCode() + methodName.hashCode();
    }
    
    @Override
    public boolean equals(Object obj) {
      if (!(obj instanceof Method)) return false;
      Method other = (Method) obj;
      return (className.equals(other.className) && methodName
          .equals(other.methodName));
    }
    
    @Override
    public String toString() {
      return className + '.' + methodName;
    }
  }
  
  public static class CoreInfo {
    static int maxCoreNum;
    String shortId;
    String url;
    Map coreProps;
  }
  
  Map coreInfoMap = new WeakHashMap<>();
  
  public void appendThread(StringBuilder sb) {
    Thread th = Thread.currentThread();
    
    // NOTE: LogRecord.getThreadID is *not* equal to Thread.getId()
    sb.append(" T");
    sb.append(th.getId());
  }

  @Override
  public String toSerializable(LogEvent event) {
    return _format(event);
  }
  
  public String _format(LogEvent event) {
    String message = event.getMessage().getFormattedMessage();
    if (message == null) {
      message = "";
    }
    StringBuilder sb = new StringBuilder(message.length() + 80);
    
    long now = event.getTimeMillis();
    long timeFromStart = now - startTime;
    lastTime = now;
    String shortClassName = getShortClassName(event.getSource().getClassName(), event.getSource().getMethodName());
    
    /***
     * sb.append(timeFromStart).append(' ').append(timeSinceLast);
     * sb.append(' ');
     * sb.append(record.getSourceClassName()).append('.').append(
     * record.getSourceMethodName()); sb.append(' ');
     * sb.append(record.getLevel());
     ***/
    
    SolrRequestInfo requestInfo = SolrRequestInfo.getRequestInfo();

    SolrCore core;
    try (SolrQueryRequest req = (requestInfo == null) ? null : requestInfo.getReq()) {
      core = (req == null) ? null : req.getCore();
    }
    ZkController zkController;
    CoreInfo info = null;
    
    if (core != null) {
      info = coreInfoMap.get(core.hashCode());
      if (info == null) {
        info = new CoreInfo();
        info.shortId = "C" + Integer.toString(CoreInfo.maxCoreNum++);
        coreInfoMap.put(core.hashCode(), info);
        
        if (sb.length() == 0) sb.append("ASYNC ");
        sb.append(" NEW_CORE ").append(info.shortId);
        sb.append(" name=").append(core.getName());
        sb.append(" ").append(core);
      }

      zkController = core.getCoreContainer().getZkController();
      if (zkController != null) {
        if (info.url == null) {
          info.url = zkController.getBaseUrl() + "/" + core.getName();
          sb.append(" url=").append(info.url).append(" node=").append(zkController.getNodeName());
        }
        
        Map coreProps = getReplicaProps(zkController, core);
        if (info.coreProps == null || !coreProps.equals(info.coreProps)) {
          info.coreProps = coreProps;
          final String corePropsString = "coll:"
              + core.getCoreDescriptor().getCloudDescriptor()
                  .getCollectionName() + " core:" + core.getName() + " props:"
              + coreProps;
          sb.append(" ").append(info.shortId).append("_STATE=").append(corePropsString);
        }
      }
    }
    
    if (sb.length() > 0) sb.append('\n');
    sb.append(timeFromStart);

    // sb.append("\nL").append(record.getSequenceNumber()); // log number is
    // useful for sequencing when looking at multiple parts of a log file, but
    // ms since start should be fine.
    appendThread(sb);

    appendMDC(sb);

    // todo: should be able to get port from core container for non zk tests

    if (info != null) {
      sb.append(' ').append(info.shortId); // core
    }

    if (shortClassName.length() > 0) {
      sb.append(' ').append(shortClassName);
    }

    if (event.getLevel() != Level.INFO) {
      sb.append(' ').append(event.getLevel());
    }
    
    sb.append(' ');
    appendMultiLineString(sb, message);
    Throwable th = event.getThrown();
    
    if (th != null) {
      sb.append(' ');
      String err = SolrException.toStr(th);
      String ignoredMsg = SolrException.doIgnore(th, err);
      if (ignoredMsg != null) {
        sb.append(ignoredMsg);
      } else {
        sb.append(err);
      }
    }
    
    
    sb.append('\n');
    
    /***
     * Isn't core specific... prob better logged from zkController if (info !=
     * null) { ClusterState clusterState = zkController.getClusterState(); if
     * (info.clusterState != clusterState) { // something has changed in the
     * matrix... sb.append(zkController.getBaseUrl() +
     * " sees new ClusterState:"); } }
     ***/
    
    return sb.toString();
  }

  private Map getReplicaProps(ZkController zkController, SolrCore core) {
    final String collectionName = core.getCoreDescriptor().getCloudDescriptor().getCollectionName();
    DocCollection collection = zkController.getClusterState().getCollectionOrNull(collectionName);
    Replica replica = collection.getReplica(zkController.getCoreNodeName(core.getCoreDescriptor()));
    if (replica != null) {
      return replica.getProperties();
    }
    return Collections.EMPTY_MAP;
  }
  
  private void addFirstLine(StringBuilder sb, String msg) {
    // INFO: [] webapp=/solr path=/select params={q=foobarbaz} hits=0 status=0
    // QTime=1
    
    if (!shorterFormat || !msg.startsWith("[")) {
      sb.append(msg);
      return;
    }
    
    int idx = msg.indexOf(']');
    if (idx < 0 || !msg.startsWith(" webapp=", idx + 1)) {
      sb.append(msg);
      return;
    }
    
    idx = msg.indexOf(' ', idx + 8); // space after webapp=
    if (idx < 0) {
      sb.append(msg);
      return;
    }
    idx = msg.indexOf('=', idx + 1); // = in path=
    if (idx < 0) {
      sb.append(msg);
      return;
    }
    
    int idx2 = msg.indexOf(' ', idx + 1);
    if (idx2 < 0) {
      sb.append(msg);
      return;
    }
    
    sb.append(msg.substring(idx + 1, idx2 + 1)); // path
    
    idx = msg.indexOf("params=", idx2);
    if (idx < 0) {
      sb.append(msg.substring(idx2));
    } else {
      sb.append(msg.substring(idx + 7));
    }
  }
  
  private void appendMultiLineString(StringBuilder sb, String msg) {
    int idx = msg.indexOf('\n');
    if (idx < 0) {
      addFirstLine(sb, msg);
      return;
    }
    
    int lastIdx = -1;
    for (;;) {
      if (idx < 0) {
        if (lastIdx == -1) {
          addFirstLine(sb, msg.substring(lastIdx + 1));
        } else {
          sb.append(msg.substring(lastIdx + 1));
        }
        break;
      }
      if (lastIdx == -1) {
        addFirstLine(sb, msg.substring(lastIdx + 1, idx));
      } else {
        sb.append(msg.substring(lastIdx + 1, idx));
      }
      
      sb.append("\n\t");
      lastIdx = idx;
      idx = msg.indexOf('\n', lastIdx + 1);
    }
  }
  
  // TODO: name this better... it's only for cloud tests where every core
  // container has just one solr server so Port/Core are fine
  public boolean shorterFormat = false;
  
  public void setShorterFormat() {
    shorterFormat = true;
    // looking at /update is enough... we don't need "UPDATE /update"
    methodAlias.put(new Method(
        "org.apache.solr.update.processor.LogUpdateProcessor", "finish"), "");
  }

  private Method classAndMethod = new Method(null, null); // don't need to be
                                                          // thread safe
  
  private String getShortClassName(String name, String method) {
    classAndMethod.className = name;
    classAndMethod.methodName = method;
    
    String out = methodAlias.get(classAndMethod);
    if (out != null) return out;
    
    StringBuilder sb = new StringBuilder();
    
    int lastDot = name.lastIndexOf('.');
    if (lastDot < 0) return name + '.' + method;
    
    int prevIndex = -1;
    for (;;) {
      char ch = name.charAt(prevIndex + 1);
      sb.append(ch);
      int idx = name.indexOf('.', prevIndex + 1);
      ch = name.charAt(idx + 1);
      if (idx >= lastDot || Character.isUpperCase(ch)) {
        sb.append(name.substring(idx));
        break;
      }
      prevIndex = idx;
    }
    
    return sb.toString() + '.' + method;
  }


  private void appendMDC(StringBuilder sb) {
    if (!StringUtils.isEmpty(MDC.get(NODE_NAME_PROP)))  {
      sb.append(" n:").append(MDC.get(NODE_NAME_PROP));
    }
    if (!StringUtils.isEmpty(MDC.get(COLLECTION_PROP)))  {
      sb.append(" c:").append(MDC.get(COLLECTION_PROP));
    }
    if (!StringUtils.isEmpty(MDC.get(SHARD_ID_PROP))) {
      sb.append(" s:").append(MDC.get(SHARD_ID_PROP));
    }
    if (!StringUtils.isEmpty(MDC.get(REPLICA_PROP))) {
      sb.append(" r:").append(MDC.get(REPLICA_PROP));
    }
    if (!StringUtils.isEmpty(MDC.get(CORE_NAME_PROP))) {
      sb.append(" x:").append(MDC.get(CORE_NAME_PROP));
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy