/** * ThingSpeak Appender for log4j Copyright 2014, Andrew Bythell * <abythell@ieee.org> * http://angryelectron.com * * The ThingSpeak Java Client is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or (at your * option) any later version. * * The ThingSpeak Java Client is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. * * You should have received a copy of the GNU General Public License along with * the ThingSpeak Appender. If not, see <http://www.gnu.org/licenses/>. */ package com.angryelectron.thingspeak.log4j; import com.angryelectron.thingspeak.Channel; import com.angryelectron.thingspeak.Entry; import com.angryelectron.thingspeak.ThingSpeakException; import com.mashape.unirest.http.exceptions.UnirestException; import java.io.IOException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.spi.LoggingEvent; /** * <p> * Appender for log4j that logs messages to ThingSpeak. To prepare a channel for * logging, create a new ThingSpeak channel with three fields (names not * important):</p> * <ol> * <li>Date</li> * <li>Level</li> * <li>Message</li> * </ol> * * <p> * Then create and configure a new appender. Use * {@link #configureChannel(Integer, String, String) configureChannel} * to configure the appender, or set via log4j.properties:</p> * <ul> * <li>log4j.appender.ThingSpeak=com.angryelectron.thingspeak.log4j.ThingSpeakAppender</li> * <li>com.angryelectron.thingspeak.log4j.channelNumber = [channel number]</li> * <li>com.angryelectron.thingspeak.log4j.apiWriteKey = [channel api write key]</li> * <li>(optional) com.angryelectron.thingspeak.log4j.server = http://your.thingspeak.server</li> * </ul> * * <p> If the server is not specified, thingspeak.com will be used. Remember that * you must use an alternate server if your application logs faster than the * rate limits imposed by thingspeak.com.</p> * * <p>Next, set your root logger to use the new appender:</p> * <ul> * <li>log4j.rootLogger=INFO, ThingSpeak</li> * </ul> * */ public class ThingSpeakAppender extends AppenderSkeleton { private final Properties properties = new Properties(); private final String channelPropertyKey = "com.angryelectron.thingspeak.log4j.channelNumber"; private final String apiPropertyKey = "com.angryelectron.thingspeak.log4j.apiWriteKey"; private final String serverPropertyKey = "com.angryelectron.thingspeak.log4j.server"; private Channel channel; /** * Constructor. */ public ThingSpeakAppender() { try { properties.load(getClass().getResourceAsStream("/log4j.properties")); String apiWriteKey = properties.getProperty(apiPropertyKey); Integer channelNumber = Integer.parseInt(properties.getProperty(channelPropertyKey)); String server = properties.getProperty(serverPropertyKey); channel = new Channel(channelNumber, apiWriteKey); if (!server.isEmpty()) { channel.setUrl(server); } } catch (IOException | NumberFormatException | NullPointerException ex) { /* ignore - will be caught and logged in append() */ } } /** * Configure the channel. Use to configure the appender in code (vs. * log4j.properties). * * @param channelNumber ThingSpeak channel number. * @param apiWriteKey ThinSpeak API write key for the channel. * @param url URL of thingspeak server. If null, the public server * (thingpspeak.com) will be used. */ public void configureChannel(Integer channelNumber, String apiWriteKey, String url) { channel = new Channel(channelNumber, apiWriteKey); if (url != null) { channel.setUrl(url); } } /** * Internal. Append log messages as an entry in a ThingSpeak channel. * * @param event log4j event. */ @Override protected void append(LoggingEvent event) { Date timeStamp = new Date(event.timeStamp); DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Entry entry = new Entry(); entry.setField(1, dateFormat.format(timeStamp)); entry.setField(2, event.getLevel().toString()); entry.setField(3, event.getMessage().toString()); try { channel.update(entry); } catch (UnirestException | ThingSpeakException ex) { Logger.getLogger(ThingSpeakAppender.class.getName()).log(Level.SEVERE, null, ex); } } /** * Internal. */ @Override public void close() { /* nothing to do */ } /** * Internal. Thingspeak maps log data directly to channel "fields", so no * layout is required. * * @return false */ @Override public boolean requiresLayout() { return false; } }