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