1
2
3
4
5
6
7
8
9
10
11
12
13
14 package ch.qos.logback.classic.pattern;
15
16 import java.util.LinkedHashMap;
17 import java.util.Map;
18
19 import ch.qos.logback.classic.spi.ILoggingEvent;
20 import ch.qos.logback.core.util.OptionHelper;
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 public abstract class NamedConverter extends ClassicConverter {
39
40 private static final String DISABLE_CACHE_SYSTEM_PROPERTY = "logback.namedConverter.disableCache";
41
42 private static final int INITIAL_CACHE_SIZE = 512;
43 private static final double LOAD_FACTOR = 0.75;
44
45
46
47
48
49 private static final int MAX_ALLOWED_REMOVAL_THRESHOLD = (int) (2048 * LOAD_FACTOR);
50
51
52
53
54 private static final double CACHE_MISSRATE_TRIGGER = 0.3d;
55
56
57
58
59
60 private static final int MIN_SAMPLE_SIZE = 1024;
61
62 private static final double NEGATIVE = -1;
63 private volatile boolean cacheEnabled = true;
64
65 private final NameCache cache = new NameCache(INITIAL_CACHE_SIZE);
66
67 private Abbreviator abbreviator = null;
68
69 private volatile int cacheMisses = 0;
70 private volatile int totalCalls = 0;
71
72
73
74
75
76
77
78 protected abstract String getFullyQualifiedName(final ILoggingEvent event);
79
80 public void start() {
81
82 String disableCacheProp = OptionHelper.getSystemProperty(DISABLE_CACHE_SYSTEM_PROPERTY);
83 boolean disableCache = OptionHelper.toBoolean(disableCacheProp, false);
84 if (disableCache) {
85 addInfo("Disabling name cache via System.properties");
86 this.cacheEnabled = false;
87 }
88
89 String optStr = getFirstOption();
90 if (optStr != null) {
91 try {
92 int targetLen = Integer.parseInt(optStr);
93 if (targetLen == 0) {
94 abbreviator = new ClassNameOnlyAbbreviator();
95 } else if (targetLen > 0) {
96 abbreviator = new TargetLengthBasedClassNameAbbreviator(targetLen);
97 }
98 } catch (NumberFormatException nfe) {
99 addError("failed to parse integer string [" + optStr + "]", nfe);
100 }
101 }
102 super.start();
103 }
104
105 public String convert(ILoggingEvent event) {
106 String fqn = getFullyQualifiedName(event);
107
108 if (abbreviator == null) {
109 return fqn;
110 } else {
111 if (cacheEnabled) {
112 return viaCache(fqn);
113 } else {
114 return abbreviator.abbreviate(fqn);
115 }
116 }
117 }
118
119
120
121
122
123
124
125
126
127
128
129 private synchronized String viaCache(String fqn) {
130 totalCalls++;
131 String abbreviated = cache.get(fqn);
132 if (abbreviated == null) {
133 cacheMisses++;
134 abbreviated = abbreviator.abbreviate(fqn);
135 cache.put(fqn, abbreviated);
136 }
137 return abbreviated;
138 }
139
140 private void disableCache() {
141 if (!cacheEnabled)
142 return;
143 this.cacheEnabled = false;
144 cache.clear();
145 addInfo("Disabling cache at totalCalls=" + totalCalls);
146 }
147
148 public double getCacheMissRate() {
149 return cache.cacheMissCalculator.getCacheMissRate();
150 }
151
152 public int getCacheMisses() {
153 return cacheMisses;
154 }
155
156 private class NameCache extends LinkedHashMap<String, String> {
157
158 private static final long serialVersionUID = 1050866539278406045L;
159
160 int removalThreshold;
161 CacheMissCalculator cacheMissCalculator = new CacheMissCalculator();
162
163 NameCache(int initialCapacity) {
164 super(initialCapacity);
165 this.removalThreshold = (int) (initialCapacity * LOAD_FACTOR);
166 }
167
168
169
170
171
172 @Override
173 protected boolean removeEldestEntry(Map.Entry<String, String> entry) {
174 if (shouldDoubleRemovalThreshold()) {
175 removalThreshold *= 2;
176
177 int missRate = (int) (cacheMissCalculator.getCacheMissRate() * 100);
178
179 NamedConverter.this.addInfo("Doubling nameCache removalThreshold to " + removalThreshold
180 + " previous cacheMissRate=" + missRate + "%");
181 cacheMissCalculator.updateMilestones();
182 }
183
184 if (size() >= removalThreshold) {
185 return true;
186 } else
187 return false;
188 }
189
190 private boolean shouldDoubleRemovalThreshold() {
191
192 double rate = cacheMissCalculator.getCacheMissRate();
193
194
195 if (rate < 0)
196 return false;
197
198 if (rate < CACHE_MISSRATE_TRIGGER)
199 return false;
200
201
202 if (this.removalThreshold >= MAX_ALLOWED_REMOVAL_THRESHOLD) {
203 NamedConverter.this.disableCache();
204 return false;
205 }
206
207 return true;
208 }
209 }
210
211 class CacheMissCalculator {
212
213 int totalsMilestone = 0;
214 int cacheMissesMilestone = 0;
215
216 void updateMilestones() {
217 this.totalsMilestone = NamedConverter.this.totalCalls;
218 this.cacheMissesMilestone = NamedConverter.this.cacheMisses;
219 }
220
221 double getCacheMissRate() {
222
223 int effectiveTotal = NamedConverter.this.totalCalls - totalsMilestone;
224
225 if (effectiveTotal < MIN_SAMPLE_SIZE) {
226
227
228 return NEGATIVE;
229 }
230
231 int effectiveCacheMisses = NamedConverter.this.cacheMisses - cacheMissesMilestone;
232 return (1.0d * effectiveCacheMisses / effectiveTotal);
233 }
234 }
235
236 }