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