blob: 0280eee45337ed52f7e71d972211ce1b55f31777 [file] [log] [blame]
/*
* 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<Method,String> 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<String,Object> coreProps;
}
Map<Integer,CoreInfo> 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<String,Object> 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();
}
@SuppressWarnings({"unchecked"})
private Map<String, Object> 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));
}
}
}