1 package org.apache.velocity.runtime.parser; 2 3 /* 4 * Licensed to the Apache Software Foundation (ASF) under one 5 * or more contributor license agreements. See the NOTICE file 6 * distributed with this work for additional information 7 * regarding copyright ownership. The ASF licenses this file 8 * to you under the Apache License, Version 2.0 (the 9 * "License"); you may not use this file except in compliance 10 * with the License. You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, 15 * software distributed under the License is distributed on an 16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 * KIND, either express or implied. See the License for the 18 * specific language governing permissions and limitations 19 * under the License. 20 */ 21 22 import org.apache.velocity.runtime.parser.node.SimpleNode; 23 import org.apache.velocity.util.introspection.Info; 24 import org.slf4j.Logger; 25 import org.slf4j.LoggerFactory; 26 import org.slf4j.MDC; 27 28 import java.util.ArrayDeque; 29 import java.util.ArrayList; 30 import java.util.Deque; 31 import java.util.List; 32 33 /** 34 * <p>Track location in template files during rendering by populating the slf4j MDC tags <code>file</code>, <code>line</code> and <code>column</code>.</p> 35 * <p>An MDC-aware logger can then use this info to display the template location in the message</p> 36 * <p>For instance with webapp-slf4j-logger, it's enough to use <code>%file</code>, <code>%line</code> and <code>%column</code> in the logger format string.</p> 37 * <p>Since this feature can have a performance impact, it has to be enabled in <code>velocity.properties</code> using:</p> 38 * <pre><code>runtime.log.track_location = true</code></pre> 39 * <p>(typically in a development environment)</p> 40 * 41 * @author Claude Brisson 42 * @version $Id:$ 43 * @since 2.2 44 */ 45 46 public class LogContext 47 { 48 protected static Logger logger = LoggerFactory.getLogger("rendering"); 49 50 public static final String MDC_FILE = "file"; 51 public static final String MDC_LINE = "line"; 52 public static final String MDC_COLUMN = "column"; 53 54 private boolean trackLocation; 55 LogContext(boolean trackLocation)56 public LogContext(boolean trackLocation) 57 { 58 this.trackLocation = trackLocation; 59 } 60 61 private static ThreadLocal<Deque<StackElement>> contextStack = new ThreadLocal<Deque<StackElement>>() 62 { 63 @Override 64 public Deque<StackElement> initialValue() 65 { 66 return new ArrayDeque<>(); 67 } 68 }; 69 70 private static class StackElement 71 { StackElement(SimpleNode src, Info info)72 protected StackElement(SimpleNode src, Info info) 73 { 74 this.src = src; 75 this.info = info; 76 } 77 78 protected SimpleNode src; 79 protected int count = 1; 80 protected Info info; 81 } 82 pushLogContext(SimpleNode src, Info info)83 public void pushLogContext(SimpleNode src, Info info) 84 { 85 if (!trackLocation) 86 { 87 return; 88 } 89 Deque<StackElement> stack = contextStack.get(); 90 StackElement last = stack.peek(); 91 if (last != null && last.src == src) 92 { 93 ++last.count; 94 } 95 else 96 { 97 stack.push(new StackElement(src, info)); 98 setLogContext(info); 99 } 100 } 101 popLogContext()102 public void popLogContext() 103 { 104 if (!trackLocation) 105 { 106 return; 107 } 108 Deque<StackElement> stack = contextStack.get(); 109 StackElement last = stack.peek(); 110 if (last == null) 111 { 112 logger.error("log context is already empty"); 113 return; 114 } 115 if (--last.count == 0) 116 { 117 stack.pop(); 118 last = stack.peek(); 119 if (last == null) 120 { 121 clearLogContext(); 122 } 123 else 124 { 125 setLogContext(last.info); 126 } 127 } 128 } 129 setLogContext(Info info)130 private void setLogContext(Info info) 131 { 132 MDC.put(MDC_FILE, info.getTemplateName()); 133 MDC.put(MDC_LINE, String.valueOf(info.getLine())); 134 MDC.put(MDC_COLUMN, String.valueOf(info.getColumn())); 135 } 136 clearLogContext()137 private void clearLogContext() 138 { 139 MDC.remove(MDC_FILE); 140 MDC.remove(MDC_LINE); 141 MDC.remove(MDC_COLUMN); 142 } 143 144 private static final String STACKTRACE_LINE = " %s at %s[line %d, column %d]"; 145 getStackTrace()146 public String[] getStackTrace() 147 { 148 if (!trackLocation) 149 { 150 return null; 151 } 152 Deque<StackElement> stack = contextStack.get(); 153 List<String> levels = new ArrayList<>(); 154 for (StackElement level : stack) 155 { 156 String line = String.format(STACKTRACE_LINE, 157 level.src.literal(), 158 level.info.getTemplateName(), 159 level.info.getLine(), 160 level.info.getColumn()); 161 levels.add(line); 162 } 163 return levels.size() > 0 ? levels.toArray(new String[levels.size()]) : null; 164 } 165 } 166