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}