/* * 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.jena.sparql.mgt ; import org.apache.jena.atlas.io.IndentedLineBuffer ; import org.apache.jena.atlas.lib.StrUtils ; import org.apache.jena.graph.Node ; import org.apache.jena.graph.Triple ; import org.apache.jena.query.ARQ ; import org.apache.jena.query.Query ; import org.apache.jena.sparql.algebra.Op ; import org.apache.jena.sparql.core.BasicPattern ; import org.apache.jena.sparql.core.Quad ; import org.apache.jena.sparql.core.QuadPattern ; import org.apache.jena.sparql.path.Path ; import org.apache.jena.sparql.serializer.SerializationContext ; import org.apache.jena.sparql.sse.SSE ; import org.apache.jena.sparql.sse.writers.WriterNode ; import org.apache.jena.sparql.util.Context ; import org.slf4j.Logger ; /** * Execution logging for query processing on a per query basis. This class * provides an overlay on top of the system logging to provide control of log * message down to a per query basis. The associated logging channel must also * be enabled. * * An execution can detail the query, the algebra and every point at which the * dataset is touched. * * Caution: logging can be a significant cost for small queries and for * memory-backed datasets because of formatting the output and disk or console * output overhead. * * @see ARQ#logExec * @see ARQ#getExecutionLogging * @see ARQ#setExecutionLogging */ public class Explain { /** * Control whether messages include multiple line output. In multiple line * output, subsequent lines start with a space to help log file parsing. */ public static boolean MultiLineMode = true ; /* * The logging system provided levels: TRACE < DEBUG < INFO < WARN < ERROR < * FATAL Explain logging is always at logging level INFO. Per query: SYSTEM * > EXEC (Query) > DETAIL (Algebra) > DEBUG (every BGP) * * Control: tdb:logExec = true (all), or enum * * Document: Include setting different loggers etc for log4j. */ // Need per-query identifier. // These are the per-execution levels. /** Information level for query execution. */ public static enum InfoLevel { /** Log each query */ INFO { @Override public int level() { return 10 ; } }, /** Log each query and it's algebra form after optimization */ FINE { @Override public int level() { return 20 ; } }, /** Log query, algebra and every database access (can be expensive) */ ALL { @Override public int level() { return 30 ; } }, /** No query execution logging. */ NONE { @Override public int level() { return -1 ; } } ; abstract public int level() ; public static InfoLevel get(String name) { if ( "ALL".equalsIgnoreCase(name) ) return ALL ; if ( "FINE".equalsIgnoreCase(name) ) return FINE ; if ( "INFO".equalsIgnoreCase(name) ) return INFO ; if ( "NONE".equalsIgnoreCase(name) ) return NONE ; return null ; } } static public final Logger logExec = ARQ.getExecLogger() ; static public final Logger logInfo = ARQ.getInfoLogger() ; // // // MOVE ME to ARQConstants // public static final Symbol symLogExec = TDB.symLogExec ; // //ARQConstants.allocSymbol("logExec") ; // ---- Query public static void explain(Query query, Context context) { explain("Query", query, context) ; } public static void explain(String message, Query query, Context context) { if ( explaining(InfoLevel.INFO, logExec, context) ) { // One line or indented multiline format IndentedLineBuffer iBuff = new IndentedLineBuffer() ; if ( true ) iBuff.incIndent() ; else iBuff.setFlatMode(true) ; query.serialize(iBuff) ; String x = iBuff.asString() ; _explain(logExec, message, x, true) ; } } // ---- Algebra public static void explain(Op op, Context context) { explain("Algebra", op, context) ; } private static final boolean MultiLinesForOps = true ; private static final boolean MultiLinesForPatterns = true ; public static void explain(String message, Op op, Context context) { if ( explaining(InfoLevel.FINE, logExec, context) ) { try (IndentedLineBuffer iBuff = new IndentedLineBuffer()) { if ( MultiLinesForOps ) iBuff.incIndent() ; else iBuff.setFlatMode(true) ; op.output(iBuff) ; String x = iBuff.asString() ; _explain(logExec, message, x, true) ; } } } // ---- BGP and quads public static void explain(BasicPattern bgp, Context context) { explain("BGP", bgp, context) ; } public static void explain(String message, BasicPattern bgp, Context context) { if ( explaining(InfoLevel.ALL, logExec, context) ) { try (IndentedLineBuffer iBuff = new IndentedLineBuffer()) { if ( MultiLinesForPatterns ) iBuff.incIndent() ; formatTriples(iBuff, bgp) ; iBuff.flush() ; String str = iBuff.toString() ; _explain(logExec, message, str, false) ; } } } public static void explain(String message, QuadPattern quads, Context context) { if ( explaining(InfoLevel.ALL, logExec, context) ) { try (IndentedLineBuffer iBuff = new IndentedLineBuffer()) { if ( MultiLinesForPatterns ) iBuff.incIndent() ; formatQuads(iBuff, quads) ; iBuff.flush() ; String str = iBuff.toString() ; _explain(logExec, message, str, false) ; } } } // public static void explainHTTP(String message, String request, Context // context) // { // if ( explaining(InfoLevel.ALL, logExec,context) ) // { // IndentedLineBuffer iBuff = new IndentedLineBuffer() ; // if ( true ) // iBuff.incIndent() ; // else // iBuff.setFlatMode(true) ; // ??? // iBuff.flush() ; // String str = iBuff.toString() ; // _explain(logExec, message, str, false) ; // } // } // TEMP : quad list that looks right. // Remove when QuadPatterns roll through from ARQ. private static void formatQuads(IndentedLineBuffer out, QuadPattern quads) { SerializationContext sCxt = SSE.sCxt(SSE.getPrefixMapWrite()) ; boolean first = true ; for ( Quad qp : quads ) { if ( !first ) { if ( ! MultiLinesForPatterns ) out.print(" ") ; } else first = false ; out.print("(") ; if ( qp.getGraph() == null ) out.print("_") ; else WriterNode.output(out, qp.getGraph(), sCxt) ; out.print(" ") ; WriterNode.output(out, qp.getSubject(), sCxt) ; out.print(" ") ; WriterNode.output(out, qp.getPredicate(), sCxt) ; out.print(" ") ; WriterNode.output(out, qp.getObject(), sCxt) ; out.print(")") ; if ( MultiLinesForPatterns ) out.println() ; } } private static void formatTriples(IndentedLineBuffer out, BasicPattern triples) { SerializationContext sCxt = SSE.sCxt(SSE.getPrefixMapWrite()) ; boolean first = true ; for ( Triple qp : triples ) { if ( ! first && ! MultiLinesForPatterns ) out.print(" ") ; first = false ; WriterNode.outputPlain(out, qp, sCxt); if ( MultiLinesForPatterns ) out.println() ; } } // ---- private static void _explain(Logger logger, String reason, String explanation, boolean newlineAlways) { // "explanation" should already be indented with some whitespace while (explanation.endsWith("\n") || explanation.endsWith("\r")) explanation = StrUtils.chop(explanation) ; if ( newlineAlways || explanation.contains("\n") ) explanation = reason + "\n" + explanation ; else explanation = reason + " :: " + explanation ; _explain(logger, explanation) ; // System.out.println(explanation) ; } private static void _explain(Logger logger, String explanation) { logger.info(explanation) ; } // General information public static void explain(Context context, String message) { if ( explaining(InfoLevel.INFO, logInfo, context) ) _explain(logInfo, message) ; } public static void explain(Context context, String format, Object... args) { if ( explaining(InfoLevel.INFO, logInfo, context) ) { // Caveat: String.format is not cheap. String str = String.format(format, args) ; _explain(logInfo, str) ; } } // public static boolean explaining(InfoLevel level, Context context) // { // return explaining(level, logExec, context) ; // } public static boolean explaining(InfoLevel level, Logger logger, Context context) { if ( !_explaining(level, context) ) return false ; return logger.isInfoEnabled() ; } private static boolean _explaining(InfoLevel level, Context context) { if ( level == InfoLevel.NONE ) return false ; Object x = context.get(ARQ.symLogExec, null) ; if ( x == null ) return false ; // Enum level. if ( level.level() == InfoLevel.NONE.level() ) return false ; if ( x instanceof InfoLevel ) { InfoLevel z = (InfoLevel)x ; if ( z == InfoLevel.NONE ) return false ; return (z.level() >= level.level()) ; } if ( x instanceof String ) { String s = (String)x ; if ( s.equalsIgnoreCase("info") ) return level.equals(InfoLevel.INFO) ; if ( s.equalsIgnoreCase("fine") ) return level.equals(InfoLevel.FINE) || level.equals(InfoLevel.INFO) ; if ( s.equalsIgnoreCase("all") ) // All levels. return true ; // Backwards compatibility. if ( s.equalsIgnoreCase("true") ) return true ; if ( s.equalsIgnoreCase("none") ) return false ; } return Boolean.TRUE.equals(x) ; } // Path public static void explain(Node s, Path path, Node o, Context context) { explain("Path", s, path, o, context) ; } public static void explain(String message, Node s, Path path, Node o, Context context) { if ( explaining(InfoLevel.ALL, logExec, context) ) { String str = s + " " + path + " " + o ; _explain(logExec, message, str, false) ; } } }