001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017 package org.apache.logging.log4j.core.config; 018 019 import org.apache.logging.log4j.Level; 020 import org.apache.logging.log4j.core.config.plugins.PluginManager; 021 import org.apache.logging.log4j.core.config.plugins.PluginType; 022 import org.apache.logging.log4j.core.config.plugins.ResolverUtil; 023 import org.apache.logging.log4j.core.helpers.FileUtils; 024 import org.apache.logging.log4j.status.StatusConsoleListener; 025 import org.apache.logging.log4j.status.StatusListener; 026 import org.apache.logging.log4j.status.StatusLogger; 027 import org.codehaus.jackson.JsonNode; 028 import org.codehaus.jackson.JsonParser; 029 import org.codehaus.jackson.map.ObjectMapper; 030 031 import java.io.ByteArrayInputStream; 032 import java.io.ByteArrayOutputStream; 033 import java.io.File; 034 import java.io.FileInputStream; 035 import java.io.FileNotFoundException; 036 import java.io.FileOutputStream; 037 import java.io.IOException; 038 import java.io.InputStream; 039 import java.io.PrintStream; 040 import java.net.URI; 041 import java.net.URISyntaxException; 042 import java.util.ArrayList; 043 import java.util.Iterator; 044 import java.util.List; 045 import java.util.Locale; 046 import java.util.Map; 047 048 /** 049 * Creates a Node hierarchy from a JSON file. 050 */ 051 public class JSONConfiguration extends BaseConfiguration implements Reconfigurable { 052 053 private static final String[] VERBOSE_CLASSES = new String[] {ResolverUtil.class.getName()}; 054 055 private static final int BUF_SIZE = 16384; 056 057 private List<Status> status = new ArrayList<Status>(); 058 059 private JsonNode root; 060 061 private List<String> messages = new ArrayList<String>(); 062 063 private File configFile; 064 065 public JSONConfiguration(ConfigurationFactory.ConfigurationSource configSource) { 066 this.configFile = configSource.getFile(); 067 byte[] buffer; 068 069 try { 070 InputStream configStream = configSource.getInputStream(); 071 buffer = toByteArray(configStream); 072 configStream.close(); 073 InputStream is = new ByteArrayInputStream(buffer); 074 ObjectMapper mapper = new ObjectMapper().configure(JsonParser.Feature.ALLOW_COMMENTS, true); 075 root = mapper.readTree(is); 076 if (root.size() == 1) { 077 Iterator<JsonNode> i = root.getElements(); 078 root = i.next(); 079 } 080 processAttributes(rootNode, root); 081 Level status = Level.OFF; 082 boolean verbose = false; 083 PrintStream stream = System.out; 084 for (Map.Entry<String, String> entry : rootNode.getAttributes().entrySet()) { 085 if ("status".equalsIgnoreCase(entry.getKey())) { 086 status = Level.toLevel(getSubst().replace(entry.getValue()), null); 087 if (status == null) { 088 status = Level.ERROR; 089 messages.add("Invalid status specified: " + entry.getValue() + ". Defaulting to ERROR"); 090 } 091 } else if ("dest".equalsIgnoreCase(entry.getKey())) { 092 String dest = entry.getValue(); 093 if (dest != null) { 094 if (dest.equalsIgnoreCase("err")) { 095 stream = System.err; 096 } else { 097 try { 098 File destFile = FileUtils.fileFromURI(new URI(dest)); 099 stream = new PrintStream(new FileOutputStream(destFile)); 100 } catch (URISyntaxException use) { 101 System.err.println("Unable to write to " + dest + ". Writing to stdout"); 102 } 103 } 104 } 105 } else if ("verbose".equalsIgnoreCase(entry.getKey())) { 106 verbose = Boolean.parseBoolean(getSubst().replace(entry.getValue())); 107 } else if ("packages".equalsIgnoreCase(entry.getKey())) { 108 String[] packages = getSubst().replace(entry.getValue()).split(","); 109 for (String p : packages) { 110 PluginManager.addPackage(p); 111 } 112 } else if ("name".equalsIgnoreCase(entry.getKey())) { 113 setName(getSubst().replace(entry.getValue())); 114 } else if ("monitorInterval".equalsIgnoreCase(entry.getKey())) { 115 int interval = Integer.parseInt(getSubst().replace(entry.getValue())); 116 if (interval > 0 && configFile != null) { 117 monitor = new FileConfigurationMonitor(this, configFile, listeners, interval); 118 } 119 } 120 } 121 122 Iterator<StatusListener> statusIter = ((StatusLogger) LOGGER).getListeners(); 123 boolean found = false; 124 while (statusIter.hasNext()) { 125 StatusListener listener = statusIter.next(); 126 if (listener instanceof StatusConsoleListener) { 127 found = true; 128 ((StatusConsoleListener) listener).setLevel(status); 129 if (!verbose) { 130 ((StatusConsoleListener) listener).setFilters(VERBOSE_CLASSES); 131 } 132 } 133 } 134 if (!found && status != Level.OFF) { 135 StatusConsoleListener listener = new StatusConsoleListener(status, stream); 136 if (!verbose) { 137 listener.setFilters(VERBOSE_CLASSES); 138 } 139 ((StatusLogger) LOGGER).registerListener(listener); 140 for (String msg : messages) { 141 LOGGER.error(msg); 142 } 143 } 144 if (getName() == null) { 145 setName(configSource.getLocation()); 146 } 147 } catch (Exception ex) { 148 LOGGER.error("Error parsing " + configSource.getLocation(), ex); 149 ex.printStackTrace(); 150 } 151 } 152 153 @Override 154 public void setup() { 155 Iterator<Map.Entry<String, JsonNode>> iter = root.getFields(); 156 List<Node> children = rootNode.getChildren(); 157 while (iter.hasNext()) { 158 Map.Entry<String, JsonNode> entry = iter.next(); 159 JsonNode n = entry.getValue(); 160 if (n.isObject()) { 161 LOGGER.debug("Processing node for object " + entry.getKey()); 162 children.add(constructNode(entry.getKey(), rootNode, n)); 163 } else if (n.isArray()) { 164 LOGGER.error("Arrays are not supported at the root configuration."); 165 } 166 } 167 LOGGER.debug("Completed parsing configuration"); 168 if (status.size() > 0) { 169 for (Status s : status) { 170 LOGGER.error("Error processing element " + s.name + ": " + s.errorType); 171 } 172 return; 173 } 174 } 175 176 public Configuration reconfigure() { 177 if (configFile != null) { 178 try { 179 ConfigurationFactory.ConfigurationSource source = 180 new ConfigurationFactory.ConfigurationSource(new FileInputStream(configFile), configFile); 181 return new JSONConfiguration(source); 182 } catch (FileNotFoundException ex) { 183 LOGGER.error("Cannot locate file " + configFile, ex); 184 } 185 } 186 return null; 187 } 188 189 private Node constructNode(String name, Node parent, JsonNode jsonNode) { 190 PluginType type = getPluginManager().getPluginType(name); 191 Node node = new Node(parent, name, type); 192 processAttributes(node, jsonNode); 193 Iterator<Map.Entry<String, JsonNode>> iter = jsonNode.getFields(); 194 List<Node> children = node.getChildren(); 195 while (iter.hasNext()) { 196 Map.Entry<String, JsonNode> entry = iter.next(); 197 JsonNode n = entry.getValue(); 198 if (n.isArray() || n.isObject()) { 199 if (type == null) { 200 status.add(new Status(name, n, ErrorType.CLASS_NOT_FOUND)); 201 } 202 if (n.isArray()) { 203 LOGGER.debug("Processing node for array " + entry.getKey()); 204 for (int i = 0; i < n.size(); ++i) { 205 String pluginType = getType(n.get(i), entry.getKey()); 206 PluginType entryType = getPluginManager().getPluginType(pluginType); 207 Node item = new Node(node, entry.getKey(), entryType); 208 processAttributes(item, n.get(i)); 209 if (pluginType.equals(entry.getKey())) { 210 LOGGER.debug("Processing " + entry.getKey() + "[" + i + "]"); 211 } else { 212 LOGGER.debug("Processing " + pluginType + " " + entry.getKey() + "[" + i + "]"); 213 } 214 Iterator<Map.Entry<String, JsonNode>> itemIter = n.get(i).getFields(); 215 List<Node> itemChildren = item.getChildren(); 216 while (itemIter.hasNext()) { 217 Map.Entry<String, JsonNode> itemEntry = itemIter.next(); 218 if (itemEntry.getValue().isObject()) { 219 LOGGER.debug("Processing node for object " + itemEntry.getKey()); 220 itemChildren.add(constructNode(itemEntry.getKey(), item, itemEntry.getValue())); 221 } 222 } 223 children.add(item); 224 } 225 } else { 226 LOGGER.debug("Processing node for object " + entry.getKey()); 227 children.add(constructNode(entry.getKey(), node, n)); 228 } 229 } 230 } 231 232 String t; 233 if (type == null) { 234 t = "null"; 235 } else { 236 t = type.getElementName() + ":" + type.getPluginClass(); 237 } 238 239 String p = node.getParent() == null ? "null" : node.getParent().getName() == null ? 240 "root" : node.getParent().getName(); 241 LOGGER.debug("Returning " + node.getName() + " with parent " + p + " of type " + t); 242 return node; 243 } 244 245 private String getType(JsonNode node, String name) { 246 Iterator<Map.Entry<String, JsonNode>> iter = node.getFields(); 247 while (iter.hasNext()) { 248 Map.Entry<String, JsonNode> entry = iter.next(); 249 if (entry.getKey().equalsIgnoreCase("type")) { 250 JsonNode n = entry.getValue(); 251 if (n.isValueNode()) { 252 return n.asText(); 253 } 254 } 255 } 256 return name; 257 } 258 259 private void processAttributes(Node parent, JsonNode node) { 260 Map<String, String> attrs = parent.getAttributes(); 261 Iterator<Map.Entry<String, JsonNode>> iter = node.getFields(); 262 while (iter.hasNext()) { 263 Map.Entry<String, JsonNode> entry = iter.next(); 264 if (!entry.getKey().equalsIgnoreCase("type")) { 265 JsonNode n = entry.getValue(); 266 if (n.isValueNode()) { 267 attrs.put(entry.getKey(), n.asText()); 268 } 269 } 270 } 271 } 272 273 protected byte[] toByteArray(InputStream is) throws IOException { 274 ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 275 276 int nRead; 277 byte[] data = new byte[BUF_SIZE]; 278 279 while ((nRead = is.read(data, 0, data.length)) != -1) { 280 buffer.write(data, 0, nRead); 281 } 282 283 return buffer.toByteArray(); 284 } 285 286 /** 287 * The error that occurred. 288 */ 289 private enum ErrorType { 290 CLASS_NOT_FOUND 291 } 292 293 /** 294 * Status for recording errors. 295 */ 296 private class Status { 297 private JsonNode node; 298 private String name; 299 private ErrorType errorType; 300 301 public Status(String name, JsonNode node, ErrorType errorType) { 302 this.name = name; 303 this.node = node; 304 this.errorType = errorType; 305 } 306 } 307 }