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.core.net.Advertiser; 025 import org.apache.logging.log4j.status.StatusConsoleListener; 026 import org.apache.logging.log4j.status.StatusListener; 027 import org.apache.logging.log4j.status.StatusLogger; 028 import org.codehaus.jackson.JsonNode; 029 import org.codehaus.jackson.JsonParser; 030 import org.codehaus.jackson.map.ObjectMapper; 031 032 import java.io.ByteArrayInputStream; 033 import java.io.ByteArrayOutputStream; 034 import java.io.File; 035 import java.io.FileInputStream; 036 import java.io.FileNotFoundException; 037 import java.io.FileOutputStream; 038 import java.io.IOException; 039 import java.io.InputStream; 040 import java.io.PrintStream; 041 import java.net.URI; 042 import java.net.URISyntaxException; 043 import java.util.ArrayList; 044 import java.util.Iterator; 045 import java.util.List; 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 final List<Status> status = new ArrayList<Status>(); 058 059 private JsonNode root; 060 061 private final List<String> messages = new ArrayList<String>(); 062 063 private final File configFile; 064 065 public JSONConfiguration(final ConfigurationFactory.ConfigurationSource configSource) { 066 this.configFile = configSource.getFile(); 067 byte[] buffer; 068 069 try { 070 final InputStream configStream = configSource.getInputStream(); 071 buffer = toByteArray(configStream); 072 configStream.close(); 073 final InputStream is = new ByteArrayInputStream(buffer); 074 final ObjectMapper mapper = new ObjectMapper().configure(JsonParser.Feature.ALLOW_COMMENTS, true); 075 root = mapper.readTree(is); 076 if (root.size() == 1) { 077 final 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 (final 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 final String dest = entry.getValue(); 093 if (dest != null) { 094 if (dest.equalsIgnoreCase("err")) { 095 stream = System.err; 096 } else { 097 try { 098 final File destFile = FileUtils.fileFromURI(new URI(dest)); 099 stream = new PrintStream(new FileOutputStream(destFile)); 100 } catch (final 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 final String[] packages = getSubst().replace(entry.getValue()).split(","); 109 for (final 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 final int interval = Integer.parseInt(getSubst().replace(entry.getValue())); 116 if (interval > 0 && configFile != null) { 117 monitor = new FileConfigurationMonitor(this, configFile, listeners, interval); 118 } 119 } else if ("advertiser".equalsIgnoreCase(entry.getKey())) { 120 final String advertiserString = getSubst().replace(entry.getValue()); 121 if (advertiserString != null) 122 { 123 final PluginType type = getPluginManager().getPluginType(advertiserString); 124 if (type != null) 125 { 126 final Class<Advertiser> clazz = type.getPluginClass(); 127 advertiser = clazz.newInstance(); 128 } 129 } 130 } 131 } 132 133 final Iterator<StatusListener> statusIter = ((StatusLogger) LOGGER).getListeners(); 134 boolean found = false; 135 while (statusIter.hasNext()) { 136 final StatusListener listener = statusIter.next(); 137 if (listener instanceof StatusConsoleListener) { 138 found = true; 139 ((StatusConsoleListener) listener).setLevel(status); 140 if (!verbose) { 141 ((StatusConsoleListener) listener).setFilters(VERBOSE_CLASSES); 142 } 143 } 144 } 145 if (!found && status != Level.OFF) { 146 final StatusConsoleListener listener = new StatusConsoleListener(status, stream); 147 if (!verbose) { 148 listener.setFilters(VERBOSE_CLASSES); 149 } 150 ((StatusLogger) LOGGER).registerListener(listener); 151 for (final String msg : messages) { 152 LOGGER.error(msg); 153 } 154 } 155 if (getName() == null) { 156 setName(configSource.getLocation()); 157 } 158 } catch (final Exception ex) { 159 LOGGER.error("Error parsing " + configSource.getLocation(), ex); 160 ex.printStackTrace(); 161 } 162 } 163 164 @Override 165 public void setup() { 166 final Iterator<Map.Entry<String, JsonNode>> iter = root.getFields(); 167 final List<Node> children = rootNode.getChildren(); 168 while (iter.hasNext()) { 169 final Map.Entry<String, JsonNode> entry = iter.next(); 170 final JsonNode n = entry.getValue(); 171 if (n.isObject()) { 172 LOGGER.debug("Processing node for object " + entry.getKey()); 173 children.add(constructNode(entry.getKey(), rootNode, n)); 174 } else if (n.isArray()) { 175 LOGGER.error("Arrays are not supported at the root configuration."); 176 } 177 } 178 LOGGER.debug("Completed parsing configuration"); 179 if (status.size() > 0) { 180 for (final Status s : status) { 181 LOGGER.error("Error processing element " + s.name + ": " + s.errorType); 182 } 183 return; 184 } 185 } 186 187 public Configuration reconfigure() { 188 if (configFile != null) { 189 try { 190 final ConfigurationFactory.ConfigurationSource source = 191 new ConfigurationFactory.ConfigurationSource(new FileInputStream(configFile), configFile); 192 return new JSONConfiguration(source); 193 } catch (final FileNotFoundException ex) { 194 LOGGER.error("Cannot locate file " + configFile, ex); 195 } 196 } 197 return null; 198 } 199 200 private Node constructNode(final String name, final Node parent, final JsonNode jsonNode) { 201 final PluginType type = getPluginManager().getPluginType(name); 202 final Node node = new Node(parent, name, type); 203 processAttributes(node, jsonNode); 204 final Iterator<Map.Entry<String, JsonNode>> iter = jsonNode.getFields(); 205 final List<Node> children = node.getChildren(); 206 while (iter.hasNext()) { 207 final Map.Entry<String, JsonNode> entry = iter.next(); 208 final JsonNode n = entry.getValue(); 209 if (n.isArray() || n.isObject()) { 210 if (type == null) { 211 status.add(new Status(name, n, ErrorType.CLASS_NOT_FOUND)); 212 } 213 if (n.isArray()) { 214 LOGGER.debug("Processing node for array " + entry.getKey()); 215 for (int i = 0; i < n.size(); ++i) { 216 final String pluginType = getType(n.get(i), entry.getKey()); 217 final PluginType entryType = getPluginManager().getPluginType(pluginType); 218 final Node item = new Node(node, entry.getKey(), entryType); 219 processAttributes(item, n.get(i)); 220 if (pluginType.equals(entry.getKey())) { 221 LOGGER.debug("Processing " + entry.getKey() + "[" + i + "]"); 222 } else { 223 LOGGER.debug("Processing " + pluginType + " " + entry.getKey() + "[" + i + "]"); 224 } 225 final Iterator<Map.Entry<String, JsonNode>> itemIter = n.get(i).getFields(); 226 final List<Node> itemChildren = item.getChildren(); 227 while (itemIter.hasNext()) { 228 final Map.Entry<String, JsonNode> itemEntry = itemIter.next(); 229 if (itemEntry.getValue().isObject()) { 230 LOGGER.debug("Processing node for object " + itemEntry.getKey()); 231 itemChildren.add(constructNode(itemEntry.getKey(), item, itemEntry.getValue())); 232 } 233 } 234 children.add(item); 235 } 236 } else { 237 LOGGER.debug("Processing node for object " + entry.getKey()); 238 children.add(constructNode(entry.getKey(), node, n)); 239 } 240 } 241 } 242 243 String t; 244 if (type == null) { 245 t = "null"; 246 } else { 247 t = type.getElementName() + ":" + type.getPluginClass(); 248 } 249 250 final String p = node.getParent() == null ? "null" : node.getParent().getName() == null ? 251 "root" : node.getParent().getName(); 252 LOGGER.debug("Returning " + node.getName() + " with parent " + p + " of type " + t); 253 return node; 254 } 255 256 private String getType(final JsonNode node, final String name) { 257 final Iterator<Map.Entry<String, JsonNode>> iter = node.getFields(); 258 while (iter.hasNext()) { 259 final Map.Entry<String, JsonNode> entry = iter.next(); 260 if (entry.getKey().equalsIgnoreCase("type")) { 261 final JsonNode n = entry.getValue(); 262 if (n.isValueNode()) { 263 return n.asText(); 264 } 265 } 266 } 267 return name; 268 } 269 270 private void processAttributes(final Node parent, final JsonNode node) { 271 final Map<String, String> attrs = parent.getAttributes(); 272 final Iterator<Map.Entry<String, JsonNode>> iter = node.getFields(); 273 while (iter.hasNext()) { 274 final Map.Entry<String, JsonNode> entry = iter.next(); 275 if (!entry.getKey().equalsIgnoreCase("type")) { 276 final JsonNode n = entry.getValue(); 277 if (n.isValueNode()) { 278 attrs.put(entry.getKey(), n.asText()); 279 } 280 } 281 } 282 } 283 284 protected byte[] toByteArray(final InputStream is) throws IOException { 285 final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 286 287 int nRead; 288 final byte[] data = new byte[BUF_SIZE]; 289 290 while ((nRead = is.read(data, 0, data.length)) != -1) { 291 buffer.write(data, 0, nRead); 292 } 293 294 return buffer.toByteArray(); 295 } 296 297 /** 298 * The error that occurred. 299 */ 300 private enum ErrorType { 301 CLASS_NOT_FOUND 302 } 303 304 /** 305 * Status for recording errors. 306 */ 307 private class Status { 308 private final JsonNode node; 309 private final String name; 310 private final ErrorType errorType; 311 312 public Status(final String name, final JsonNode node, final ErrorType errorType) { 313 this.name = name; 314 this.node = node; 315 this.errorType = errorType; 316 } 317 } 318 }