Newer
Older
package uws.service.log;
/*
* This file is part of UWSLibrary.
*
* UWSLibrary is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* UWSLibrary is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with UWSLibrary. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2012-2015 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
* Astronomisches Rechen Institut (ARI)
*/
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.Map.Entry;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import uws.UWSException;
import uws.UWSToolBox;
import uws.job.UWSJob;
import uws.job.user.JobOwner;
import uws.service.UWS;
import uws.service.file.UWSFileManager;
/**
* <p>Default implementation of {@link UWSLog} interface which lets logging any message about a UWS.</p>
*
* @author Grégory Mantelet (CDS;ARI)
* @version 4.1 (04/2015)
*/
public class DefaultUWSLog implements UWSLog {
/** Format to use to serialize all encountered dates. */
private DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
protected final UWS uws;
protected final UWSFileManager fileManager;
protected final PrintWriter defaultOutput;
/** <p>Minimum level that a message must have in order to be logged.</p>
* <p>The default behavior is the following:</p>
* <ul>
* <li><b>DEBUG</b>: every messages are logged.</li>
* <li><b>INFO</b>: every messages EXCEPT DEBUG are logged.</li>
* <li><b>WARNING</b>: every messages EXCEPT DEBUG and INFO are logged.</li>
* <li><b>ERROR</b>: only ERROR and FATAL messages are logged.</li>
* <li><b>FATAL</b>: only FATAL messages are logged.</li>
* </ul>
* @since 4.1 */
protected LogLevel minLogLevel = LogLevel.DEBUG;
/**
* <p>Builds a {@link UWSLog} which will use the file manager
* of the given UWS to get the log output (see {@link UWSFileManager#getLogOutput(UWSLogType)}).</p>
*
* <p><i><u>note 1</u>: This constructor is particularly useful if the file manager of the given UWS may change.</i></p>
* <p><i><u>note 2</u>: If no output can be found in the file manager (or if there is no file manager),
* the standard error output ({@link System#err}) will be chosen automatically for all log messages.</i></p>
*
* @param uws A UWS.
*/
public DefaultUWSLog(final UWS uws){
this.uws = uws;
fileManager = null;
defaultOutput = null;
}
/**
* <p>Builds a {@link UWSLog} which will use the given file
* manager to get the log output (see {@link UWSFileManager#getLogOutput(UWSLogType)}).</p>
*
* <p><i><u>note 1</u>: This constructor is particularly useful if the way of managing log output may change in the given file manager.
* Indeed, the output may change in function of the type of message to log ({@link UWSLogType}).</i></p>
*
* <p><i><u>note 2</u> If no output can be found in the file manager the standard error output ({@link System#err})
* will be chosen automatically for all log messages.</i></p>
*
* @param fm A UWS file manager.
*/
public DefaultUWSLog(final UWSFileManager fm){
uws = null;
fileManager = fm;
defaultOutput = null;
}
/**
* <p>Builds a {@link UWSLog} which will print all its
* messages into the given stream.</p>
*
* <p><i><u>note</u>: the given output will be used whatever is the type of message to log ({@link UWSLogType}).</i></p>
*
* @param output An output stream.
*/
gmantele
committed
public DefaultUWSLog(final OutputStream output){
uws = null;
fileManager = null;
defaultOutput = new PrintWriter(output);
}
/**
* <p>Builds a {@link UWSLog} which will print all its
* messages into the given stream.</p>
*
* <p><i><u>note</u>: the given output will be used whatever is the type of message to log ({@link UWSLogType}).</i></p>
*
* @param writer A print writer.
*/
public DefaultUWSLog(final PrintWriter writer){
uws = null;
fileManager = null;
defaultOutput = writer;
}
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
/**
* <p>Get the minimum level that a message must have in order to be logged.</p>
*
* <p>The default behavior is the following:</p>
* <ul>
* <li><b>DEBUG</b>: every messages are logged.</li>
* <li><b>INFO</b>: every messages EXCEPT DEBUG are logged.</li>
* <li><b>WARNING</b>: every messages EXCEPT DEBUG and INFO are logged.</li>
* <li><b>ERROR</b>: only ERROR and FATAL messages are logged.</li>
* <li><b>FATAL</b>: only FATAL messages are logged.</li>
* </ul>
*
* @return The minimum log level.
*
* @since 4.1
*/
public final LogLevel getMinLogLevel(){
return minLogLevel;
}
/**
* <p>Set the minimum level that a message must have in order to be logged.</p>
*
* <p>The default behavior is the following:</p>
* <ul>
* <li><b>DEBUG</b>: every messages are logged.</li>
* <li><b>INFO</b>: every messages EXCEPT DEBUG are logged.</li>
* <li><b>WARNING</b>: every messages EXCEPT DEBUG and INFO are logged.</li>
* <li><b>ERROR</b>: only ERROR and FATAL messages are logged.</li>
* <li><b>FATAL</b>: only FATAL messages are logged.</li>
* </ul>
*
* <p><i>Note:
* If the given level is NULL, this function has no effect.
* </i></p>
*
* @param newMinLevel The new minimum log level.
*
* @since 4.1
*/
public final void setMinLogLevel(final LogLevel newMinLevel){
if (newMinLevel != null)
minLogLevel = newMinLevel;
}
/**
* Gets the date formatter/parser to use for any date read/write into this logger.
* @return A date formatter/parser.
*/
gmantele
committed
public final DateFormat getDateFormat(){
return dateFormat;
}
/**
* Sets the date formatter/parser to use for any date read/write into this logger.
* @param dateFormat The date formatter/parser to use from now. (MUST BE DIFFERENT FROM NULL)
*/
gmantele
committed
public final void setDateFormat(final DateFormat dateFormat){
if (dateFormat != null)
this.dateFormat = dateFormat;
}
/**
* <p>Gets an output for the given type of message to print.</p>
*
* <p>The {@link System#err} output is used if none can be found in the {@link UWS} or the {@link UWSFileManager}
* given at the creation, or if the given output stream or writer is NULL.</p>
*
* @param level Level of the message to print (DEBUG, INFO, WARNING, ERROR or FATAL).
* @param context Context of the message to print (UWS, HTTP, JOB, THREAD).
*
* @return A writer.
*/
protected PrintWriter getOutput(final LogLevel level, final String context){
try{
if (uws != null){
if (uws.getFileManager() != null)
return uws.getFileManager().getLogOutput(level, context);
}else if (fileManager != null)
return fileManager.getLogOutput(level, context);
else if (defaultOutput != null)
return defaultOutput;
}catch(IOException ioe){
ioe.printStackTrace(System.err);
}
return new PrintWriter(System.err);
}
/* *********************** */
/* GENERAL LOGGING METHODS */
/* *********************** */
/**
* <p>Normalize a log message.</p>
*
* <p>
* Since a log entry will a tab-separated concatenation of information, additional tabulations or new-lines
* would corrupt a log entry. This function replaces such characters by one space. Only \r are definitely deleted.
* </p>
*
* @param message Log message to normalize.
*
* @return The normalized log message.
*
* @since 4.1
*/
protected String normalizeMessage(final String message){
if (message == null)
return null;
else
return message.replaceAll("[\n\t]", " ").replaceAll("\r", "");
}
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
/**
* <p>Tells whether a message with the given error level can be logged or not.</p>
*
* <p>In function of the minimum log level of this class, the default behavior is the following:</p>
* <ul>
* <li><b>DEBUG</b>: every messages are logged.</li>
* <li><b>INFO</b>: every messages EXCEPT DEBUG are logged.</li>
* <li><b>WARNING</b>: every messages EXCEPT DEBUG and INFO are logged.</li>
* <li><b>ERROR</b>: only ERROR and FATAL messages are logged.</li>
* <li><b>FATAL</b>: only FATAL messages are logged.</li>
* </ul>
*
* @param msgLevel Level of the message which has been asked to log. <i>Note: if NULL, it will be considered as DEBUG.</i>
*
* @return <i>true</i> if the message associated with the given log level can be logged, <i>false</i> otherwise.
*
* @since 4.1
*/
protected boolean canLog(LogLevel msgLevel){
// No level specified => DEBUG
if (msgLevel == null)
msgLevel = LogLevel.DEBUG;
// Decide in function of the minimum log level set in this class:
switch(minLogLevel){
case INFO:
return (msgLevel != LogLevel.DEBUG);
case WARNING:
return (msgLevel != LogLevel.DEBUG && msgLevel != LogLevel.INFO);
case ERROR:
return (msgLevel == LogLevel.ERROR || msgLevel == LogLevel.FATAL);
case FATAL:
return (msgLevel == LogLevel.FATAL);
case DEBUG:
default:
return true;
}
}
@Override
public void log(LogLevel level, final String context, final String message, final Throwable error){
log(level, context, null, null, message, null, error);
}
* <p>Logs a full message and/or error.</p>
* <p><i>Note:
* If no message and error is provided, nothing will be written.
* </i></p>
*
* @param level Level of the error (DEBUG, INFO, WARNING, ERROR, FATAL). <i>SHOULD NOT be NULL</i>
* @param context Context of the error (UWS, HTTP, THREAD, JOB). <i>MAY be NULL</i>
* @param event Context event during which this log is emitted. <i>MAY be NULL</i>
* @param ID ID of the job or HTTP request (it may also be an ID of anything else). <i>MAY BE NULL</i>
* @param message Message of the error. <i>MAY be NULL</i>
* @param addColumn Additional column to append after the message and before the stack trace.
* @param error Error at the origin of the log error/warning/fatal. <i>MAY be NULL</i>
*
* @since 4.1
protected final void log(LogLevel level, final String context, final String event, final String ID, final String message, final String addColumn, final Throwable error){
// If no message and no error is provided, nothing to log, so nothing to write:
if ((message == null || message.length() <= 0) && error == null)
return;
// If the type is missing:
if (level == null)
level = (error != null) ? LogLevel.ERROR : LogLevel.INFO;
// Log or not?
if (!canLog(level))
return;
StringBuffer buf = new StringBuffer();
// Print the date/time:
buf.append(dateFormat.format(new Date())).append('\t');
// Print the level of error (debug, info, warning, error, fatal):
buf.append(level.toString()).append('\t');
// Print the context of the error (uws, thread, job, http):
buf.append((context == null) ? "" : context).append('\t');
// Print the context event:
buf.append((event == null) ? "" : event).append('\t');
// Print an ID (jobID, requestID):
buf.append((ID == null) ? "" : ID).append('\t');
// Print the message:
if (message != null)
buf.append(normalizeMessage(message));
else if (error != null)
buf.append("[EXCEPTION ").append(error.getClass().getName()).append("] ").append(normalizeMessage(error.getMessage()));
// Print the additional column, if any:
if (addColumn != null)
buf.append('\t').append(normalizeMessage(addColumn));
// Write the whole log line:
PrintWriter out = getOutput(level, context);
out.println(buf.toString());
// Print the stack trace, if any:
printException(error, out);
out.flush();
}
/**
* Format and print the given exception inside the given writer.
*
* @param error The exception to print.
* @param out The output in which the exception must be written.
*
* @since 4.1
*/
protected void printException(final Throwable error, final PrintWriter out){
if (error != null){
if (error instanceof UWSException){
if (error.getCause() != null)
printException(error.getCause(), out);
else{
out.println("Caused by a " + error.getClass().getName());
if (error.getMessage() != null)
out.println("\t" + error.getMessage());
}
}else{
out.print("Caused by a ");
error.printStackTrace(out);
}
}
}
gmantele
committed
public void debug(String msg){
log(LogLevel.DEBUG, null, msg, null);
gmantele
committed
}
gmantele
committed
public void debug(Throwable t){
log(LogLevel.DEBUG, null, null, t);
gmantele
committed
}
gmantele
committed
public void debug(String msg, Throwable t){
log(LogLevel.DEBUG, null, msg, t);
gmantele
committed
}
gmantele
committed
public void info(String msg){
log(LogLevel.INFO, null, msg, null);
gmantele
committed
}
gmantele
committed
public void warning(String msg){
log(LogLevel.WARNING, null, msg, null);
gmantele
committed
}
gmantele
committed
public void error(String msg){
log(LogLevel.ERROR, null, msg, null);
gmantele
committed
}
gmantele
committed
public void error(Throwable t){
log(LogLevel.ERROR, null, null, t);
gmantele
committed
}
gmantele
committed
@Override
public void error(String msg, Throwable t){
log(LogLevel.ERROR, null, msg, t);
gmantele
committed
}
/* ************* */
/* HTTP ACTIVITY */
/* ************* */
* <p>A message/error logged with this function will have the following format:</p>
* <pre><TIMESTAMP> <LEVEL> HTTP REQUEST_RECEIVED <REQUEST_ID> <MESSAGE> <HTTP_METHOD> in <CONTENT_TYPE> at <URL> from <IP_ADDR> using <USER_AGENT> with parameters (<PARAM1>=<VAL1>&...)</pre>
* @see uws.service.log.UWSLog#logHttp(uws.service.log.UWSLog.LogLevel, javax.servlet.http.HttpServletRequest, java.lang.String, java.lang.String, java.lang.Throwable)
*/
@Override
public void logHttp(LogLevel level, final HttpServletRequest request, final String requestId, final String message, final Throwable error){
// IF A REQUEST IS PROVIDED, write its details after the message in a new column:
if (request != null){
// If the type is missing:
if (level == null)
level = (error != null) ? LogLevel.ERROR : LogLevel.INFO;
// Log or not?
if (!canLog(level))
return;
StringBuffer str = new StringBuffer();
// Write the request type, content type and the URL:
str.append(request.getMethod());
str.append(" as ");
if (request.getContentType() != null){
if (request.getContentType().indexOf(';') > 0)
str.append(request.getContentType().substring(0, request.getContentType().indexOf(';')));
else
str.append(request.getContentType());
}
str.append(" at ").append(request.getRequestURL());
// Write the IP address:
str.append(" from ").append(request.getRemoteAddr());
// Write the user agent:
str.append(" using ").append(request.getHeader("User-Agent") == null ? "" : request.getHeader("User-Agent"));
// Write the posted parameters:
str.append(" with parameters (");
Map<String,String> params = UWSToolBox.getParamsMap(request);
int i = -1;
for(Entry<String,String> p : params.entrySet()){
if (++i > 0)
str.append('&');
str.append(p.getKey()).append('=').append((p.getValue() != null) ? p.getValue() : "");
str.append(')');
// Send the log message to the log file:
log(level, "HTTP", "REQUEST_RECEIVED", requestId, (message != null ? message : str.toString()), (message != null ? str.toString() : null), error);
}
// OTHERWISE, just write the given message:
else
log(level, "HTTP", "REQUEST_RECEIVED", requestId, message, null, error);
}
/**
* <p>A message/error logged with this function will have the following format:</p>
* <pre><TIMESTAMP> <LEVEL> HTTP RESPONSE_SENT <REQUEST_ID> <MESSAGE> HTTP-<STATUS_CODE> to the user <USER> as <CONTENT_TYPE></pre>
* <p>,where <USER> may be either "(id:<USER_ID>;pseudo:<USER_PSEUDO>)" or "ANONYMOUS".</p>
*
* @see uws.service.log.UWSLog#logHttp(uws.service.log.UWSLog.LogLevel, javax.servlet.http.HttpServletResponse, java.lang.String, uws.job.user.JobOwner, java.lang.String, java.lang.Throwable)
*/
@Override
public void logHttp(LogLevel level, HttpServletResponse response, String requestId, JobOwner user, String message, Throwable error){
if (response != null){
// If the type is missing:
if (level == null)
level = (error != null) ? LogLevel.ERROR : LogLevel.INFO;
// Log or not?
if (!canLog(level))
return;
StringBuffer str = new StringBuffer();
// Write the response status code:
str.append("HTTP-").append(response.getStatus());
// Write the user to whom the response is sent:
str.append(" to the user ");
if (user != null){
str.append("(id:").append(user.getID());
if (user.getPseudo() != null)
str.append(";pseudo:").append(user.getPseudo());
str.append(')');
str.append("ANONYMOUS");
// Write the response's MIME type:
if (response.getContentType() != null)
str.append(" as ").append(response.getContentType());
// Send the log message to the log file:
log(level, "HTTP", "RESPONSE_SENT", requestId, message, str.toString(), error);
// OTHERWISE, just write the given message:
else
log(level, "HTTP", "RESPONSE_SENT", requestId, message, null, error);
/* ************ */
/* UWS ACTIVITY */
/* ************ */
public void logUWS(LogLevel level, Object obj, String event, String message, Throwable error){
// If the type is missing:
if (level == null)
level = (error != null) ? LogLevel.ERROR : LogLevel.INFO;
// Log or not?
if (!canLog(level))
return;
// CASE "BACKUPED": Append to the message the backup report:
String report = null;
if (event != null && event.equalsIgnoreCase("BACKUPED") && obj != null && obj.getClass().getName().equals("[I")){
int[] backupReport = (int[])obj;
report = "(" + backupReport[0] + "/" + backupReport[1] + " jobs backuped ; " + backupReport[2] + "/" + backupReport[3] + " users backuped)";
}else if (event != null && event.equalsIgnoreCase("RESTORED") && obj != null && obj.getClass().getName().equals("[I")){
int[] restoreReport = (int[])obj;
report = "(" + restoreReport[0] + "/" + restoreReport[1] + " jobs restored ; " + restoreReport[2] + "/" + restoreReport[3] + " users restored)";
}
// Log the message
log(level, "UWS", event, null, message, report, error);
/* ************ */
/* JOB ACTIVITY */
/* ************ */
public void logJob(LogLevel level, UWSJob job, String event, String message, Throwable error){
log(level, "JOB", event, (job == null) ? null : job.getJobId(), message, null, error);
/* ********************** */
/* THREAD STATUS MESSAGES */
/* ********************** */
public void logThread(LogLevel level, Thread thread, String event, String message, Throwable error){
if (thread != null){
// If the type is missing:
if (level == null)
level = (error != null) ? LogLevel.ERROR : LogLevel.INFO;
// Log or not?
if (!canLog(level))
return;
StringBuffer str = new StringBuffer();
// Write the thread name and ID:
str.append(thread.getName()).append(" (thread ID: ").append(thread.getId()).append(")");
// Write the thread state:
str.append(" is ").append(thread.getState());
// Write its thread group name:
str.append(" in the group " + thread.getThreadGroup().getName());
// Write the number of active threads:
str.append(" where ").append(thread.getThreadGroup().activeCount()).append(" threads are active");
log(level, "THREAD", event, thread.getName(), message, str.toString(), error);
}else
log(level, "THREAD", event, null, message, null, error);