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}