001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2010–2011 ApexIdentity Inc. 015 * Portions Copyright 2011-2014 ForgeRock AS. 016 */ 017 018package org.forgerock.openig.log; 019 020import static org.forgerock.openig.util.Json.*; 021import static org.forgerock.util.Utils.*; 022 023import java.io.File; 024import java.io.FileOutputStream; 025import java.io.IOException; 026import java.io.PrintStream; 027import java.nio.charset.Charset; 028 029import org.forgerock.json.fluent.JsonValueException; 030import org.forgerock.openig.heap.GenericHeaplet; 031import org.forgerock.openig.heap.HeapException; 032import org.forgerock.openig.heap.Name; 033 034/** 035 * A sink that writes log entries to a file. 036 */ 037public class FileLogSink implements LogSink { 038 039 /** 040 * Default {@link Charset} to use on the output file. 041 */ 042 public static final Charset UTF_8 = Charset.forName("UTF-8"); 043 044 /** File where the entries will be written to. */ 045 private final File file; 046 047 /** Character set to encode log output with (default: UTF-8). */ 048 private final Charset charset; 049 050 /** The level of log entries to display in the file (default: {@link LogLevel#INFO INFO}). */ 051 private LogLevel level = LogLevel.INFO; 052 053 /** Wraps the file output for writing entries. */ 054 private PrintStream stream; 055 056 /** Wraps the previous stream, prefixing all lines with a hash ('#') to have them appear as comments. */ 057 private PrintStream comment; 058 059 /** 060 * Builds a new FileLogSink writing entries in the given log file. 061 * 062 * @param file 063 * output where entries will be written (default to UTF-8 Charset) 064 */ 065 public FileLogSink(final File file) { 066 this(file, UTF_8); 067 } 068 069 /** 070 * Builds a new FileLogSink writing entries in the given log file using the specified {@link Charset}. 071 * 072 * @param file 073 * output where entries will be written (default to UTF-8 Charset) 074 * @param charset 075 * Character set to encode log output with 076 */ 077 public FileLogSink(final File file, final Charset charset) { 078 this.file = file; 079 this.charset = charset; 080 } 081 082 /** 083 * Sets the level of log entries to display in the file. 084 * @param level level of log entries to display in the file 085 */ 086 public void setLevel(final LogLevel level) { 087 this.level = level; 088 } 089 090 @Override 091 public void log(LogEntry entry) { 092 if (isLoggable(entry.getSource(), entry.getLevel())) { 093 synchronized (this) { 094 try { 095 if (!file.exists() || stream == null) { 096 closeSilently(stream, comment); 097 stream = new PrintStream(new FileOutputStream(file, true), true, charset.name()); 098 comment = new HashPrefixPrintStream(stream); 099 } 100 101 // Example: "Sun Jul 20 16:17:00 EDT 1969 (INFO) Source - Message" 102 stream.printf("%Tc %s %s --- %s%n", 103 entry.getTime(), 104 entry.getLevel().name(), 105 entry.getSource().getLeaf(), 106 entry.getMessage()); 107 108 // Print the exception data (if any) on the commenting stream 109 if ("throwable".equals(entry.getType()) && (entry.getData() instanceof Throwable)) { 110 ((Throwable) entry.getData()).printStackTrace(comment); 111 } 112 } catch (IOException ioe) { 113 // not much else we can do 114 System.err.println(ioe.getMessage()); 115 } 116 } 117 } 118 } 119 120 @Override 121 public boolean isLoggable(Name source, LogLevel level) { 122 return (level.compareTo(this.level) >= 0); 123 } 124 125 /** Creates and initializes a file log sink in a heap environment. */ 126 public static class Heaplet extends GenericHeaplet { 127 @Override 128 public Object create() throws HeapException { 129 File file = new File(evaluate(config.get("file").required())); 130 try { 131 // try opening file to ensure it's writable at config time 132 FileOutputStream out = new FileOutputStream(file, true); 133 out.close(); 134 } catch (IOException ioe) { 135 throw new JsonValueException(config.get("file"), ioe); 136 } 137 FileLogSink sink = new FileLogSink(file); 138 sink.setLevel(config.get("level").defaultTo(sink.level.toString()).asEnum(LogLevel.class)); 139 return sink; 140 } 141 } 142 143 private static class HashPrefixPrintStream extends PrintStream { 144 145 private static final String HASH = "# "; 146 147 public HashPrefixPrintStream(final PrintStream delegate) { 148 super(delegate); 149 } 150 151 @Override 152 public void println() { 153 super.print(HASH); 154 super.println(); 155 } 156 157 @Override 158 public void println(final String x) { 159 super.print(HASH); 160 super.println(x); 161 } 162 163 @Override 164 public void println(final boolean x) { 165 super.print(HASH); 166 super.println(x); 167 } 168 169 @Override 170 public void println(final char x) { 171 super.print(HASH); 172 super.println(x); 173 } 174 175 @Override 176 public void println(final int x) { 177 super.print(HASH); 178 super.println(x); 179 } 180 181 @Override 182 public void println(final long x) { 183 super.print(HASH); 184 super.println(x); 185 } 186 187 @Override 188 public void println(final float x) { 189 super.print(HASH); 190 super.println(x); 191 } 192 193 @Override 194 public void println(final double x) { 195 super.print(HASH); 196 super.println(x); 197 } 198 199 @Override 200 public void println(final char[] x) { 201 super.print(HASH); 202 super.println(x); 203 } 204 205 @Override 206 public void println(final Object x) { 207 super.print(HASH); 208 super.println(x); 209 } 210 } 211}