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 java.lang.String.*;
021
022import java.io.PrintStream;
023
024import org.forgerock.openig.heap.GenericHeaplet;
025import org.forgerock.openig.heap.HeapException;
026import org.forgerock.openig.heap.Name;
027
028/**
029 * A sink that writes log entries to the standard output or error stream (depending on the object configuration).
030 */
031public class ConsoleLogSink implements LogSink {
032
033    private static final Object LOCK = new Object();
034
035    /**
036     * Where do the user want its log written to ?
037     */
038    enum Stream {
039        OUT {
040            @Override
041            PrintStream getStream(final LogEntry entry) {
042                return System.out;
043            }
044        },
045        ERR {
046            @Override
047            PrintStream getStream(final LogEntry entry) {
048                return System.err;
049            }
050        },
051        AUTO {
052            @Override
053            PrintStream getStream(final LogEntry entry) {
054                PrintStream stream = System.out;
055                if (entry.getLevel().compareTo(LogLevel.INFO) > 0) {
056                    stream = System.err;
057                }
058                return stream;
059            }
060        };
061
062        /**
063         * Returns the appropriate stream to write the entry to.
064         */
065        abstract PrintStream getStream(LogEntry entry);
066    }
067
068    /** The level of log entries to display in the console (default: {@link LogLevel#INFO INFO}). */
069    private LogLevel level = LogLevel.INFO;
070
071    /** Specify which PrintStream to use when printing a log statement. */
072    private Stream stream = Stream.ERR;
073
074    /**
075     * Sets the level of log entries to display in the console.
076     * @param level level of log entries to display in the console
077     */
078    public void setLevel(final LogLevel level) {
079        this.level = level;
080    }
081
082    /**
083     * Sets the stream to write entries to.
084     * @param stream the stream to write entries to.
085     */
086    public void setStream(final Stream stream) {
087        this.stream = stream;
088    }
089
090    @Override
091    public void log(LogEntry entry) {
092        if (isLoggable(entry.getSource(), entry.getLevel())) {
093            synchronized (LOCK) {
094                PrintStream stream = this.stream.getStream(entry);
095                writeEntry(stream, entry);
096                if ("throwable".equals(entry.getType()) && (entry.getData() instanceof Throwable)) {
097                    Throwable throwable = (Throwable) entry.getData();
098                    writeShortThrowable(stream, throwable);
099                    if (level.compareTo(LogLevel.DEBUG) <= 0) {
100                        writeStackTrace(stream, throwable);
101                    }
102                }
103                writeSeparator(stream);
104                stream.flush();
105            }
106        }
107    }
108
109    private void writeSeparator(final PrintStream stream) {
110        for (int i = 0; i < 30; i++) {
111            stream.print('-');
112        }
113        stream.println();
114    }
115
116    private void writeEntry(final PrintStream stream, final LogEntry entry) {
117        writeHeader(stream, entry.getTime(), entry.getLevel(), entry.getSource());
118        writeMessage(stream, entry.getMessage());
119    }
120
121    private void writeShortThrowable(final PrintStream stream, final Throwable throwable) {
122        // Print each of the chained exception's messages (in order)
123        Throwable current = throwable;
124        while (current != null) {
125            writeMessage(stream, format("[%25s] > %s",
126                                        current.getClass().getSimpleName(),
127                                        current.getLocalizedMessage()));
128            current = current.getCause();
129        }
130    }
131
132    private void writeStackTrace(final PrintStream stream, final Throwable throwable) {
133        stream.println();
134        throwable.printStackTrace(stream);
135    }
136
137    private void writeHeader(final PrintStream stream, final long time, final LogLevel level, final Name name) {
138        // Example: "Sun Jul 20 16:17:00 EDT 1969 (INFO) "
139        stream.printf("%Tc (%s) %s%n",
140                      time,
141                      level.name(),
142                      name.getLeaf());
143    }
144
145    private void writeMessage(final PrintStream stream, final String message) {
146        stream.println(message);
147    }
148
149    @Override
150    public boolean isLoggable(Name source, LogLevel level) {
151        return (level.compareTo(this.level) >= 0);
152    }
153
154    /**
155     * Creates and initializes a console sink in a heap environment.
156     */
157    public static class Heaplet extends GenericHeaplet {
158        @Override
159        public Object create() throws HeapException {
160            ConsoleLogSink sink = new ConsoleLogSink();
161            sink.setLevel(config.get("level").defaultTo(sink.level.toString()).asEnum(LogLevel.class));
162            sink.setStream(config.get("stream").defaultTo(sink.stream.toString()).asEnum(Stream.class));
163            return sink;
164        }
165    }
166}