• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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