| package org.apache.solr.util; |
| |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.WeakHashMap; |
| |
| import org.apache.log4j.Layout; |
| import org.apache.log4j.Level; |
| import org.apache.log4j.spi.LoggingEvent; |
| import org.apache.log4j.spi.ThrowableInformation; |
| import org.apache.solr.cloud.ZkController; |
| import org.apache.solr.common.SolrException; |
| import org.apache.solr.common.cloud.Replica; |
| import org.apache.solr.core.SolrCore; |
| import org.apache.solr.request.SolrQueryRequest; |
| import org.apache.solr.request.SolrRequestInfo; |
| |
| /* |
| * 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. |
| */ |
| |
| public class SolrLogLayout extends Layout { |
| /** |
| * 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(); |
| } |
| |
| 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 Map<String,String> classAliases = new HashMap<>(); |
| |
| public void appendThread(StringBuilder sb, LoggingEvent event) { |
| Thread th = Thread.currentThread(); |
| |
| /****** |
| * sb.append(" T="); sb.append(th.getName()).append(' '); |
| * |
| * // NOTE: tried creating a thread group around jetty but we seem to lose |
| * it and request // threads are in the normal "main" thread group |
| * ThreadGroup tg = th.getThreadGroup(); while (tg != null) { |
| * sb.append("(group_name=").append(tg.getName()).append(")"); |
| * |
| * if (tg instanceof TG) { sb.append(((TG)tg).getTag()); sb.append('/'); } |
| * try { tg = tg.getParent(); } catch (Throwable e) { tg = null; } } |
| ******/ |
| |
| // NOTE: LogRecord.getThreadID is *not* equal to Thread.getId() |
| sb.append(" T"); |
| sb.append(th.getId()); |
| } |
| |
| @Override |
| public String format(LoggingEvent event) { |
| return _format(event); |
| } |
| |
| public String _format(LoggingEvent event) { |
| String message = (String) event.getMessage(); |
| if (message == null) { |
| message = ""; |
| } |
| StringBuilder sb = new StringBuilder(message.length() + 80); |
| |
| long now = event.timeStamp; |
| long timeFromStart = now - startTime; |
| long timeSinceLast = now - lastTime; |
| lastTime = now; |
| String shortClassName = getShortClassName(event.getLocationInformation().getClassName(), |
| event.getLocationInformation().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(); |
| SolrQueryRequest req = requestInfo == null ? null : requestInfo.getReq(); |
| SolrCore core = req == null ? null : req.getCore(); |
| ZkController zkController = null; |
| 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 " + info.shortId); |
| sb.append(" name=" + core.getName()); |
| sb.append(" " + core); |
| } |
| |
| if (zkController == null) { |
| zkController = core.getCoreDescriptor().getCoreContainer() |
| .getZkController(); |
| } |
| if (zkController != null) { |
| if (info.url == null) { |
| info.url = zkController.getBaseUrl() + "/" + core.getName(); |
| sb.append(" url=" + info.url + " node=" + 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(" " + info.shortId + "_STATE=" + 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, event); |
| |
| if (info != null) { |
| sb.append(' ').append(info.shortId); // core |
| } |
| if (zkController != null) { |
| sb.append(" P").append(zkController.getHostPort()); // todo: should be |
| // able to get this |
| // from core container |
| // for non zk tests |
| } |
| |
| if (shortClassName.length() > 0) { |
| sb.append(' ').append(shortClassName); |
| } |
| |
| if (event.getLevel() != Level.INFO) { |
| sb.append(' ').append(event.getLevel()); |
| } |
| |
| sb.append(' '); |
| appendMultiLineString(sb, message); |
| ThrowableInformation thInfo = event.getThrowableInformation(); |
| if (thInfo != null) { |
| Throwable th = event.getThrowableInformation().getThrowable(); |
| 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<String,Object> getReplicaProps(ZkController zkController, SolrCore core) { |
| final String collection = core.getCoreDescriptor().getCloudDescriptor().getCollectionName(); |
| Replica replica = zkController.getClusterState().getReplica(collection, 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; |
| } |
| |
| @Override |
| public void activateOptions() {} |
| |
| @Override |
| public boolean ignoresThrowable() { |
| return false; |
| } |
| } |