blob: 3390c974987e50254e938516f4d8afa6f3a39ad9 [file] [log] [blame]
// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file *
// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance *
// * with the License. You may obtain a copy of the License at *
// * *
// * http://www.apache.org/licenses/LICENSE-2.0 *
// * *
// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an *
// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the *
// * specific language governing permissions and limitations under the License. *
// ***************************************************************************************************************************
package org.apache.juneau.rest.mock;
import static org.apache.juneau.internal.ExceptionUtils.*;
import java.io.*;
import java.util.*;
import java.util.logging.*;
import java.util.logging.Formatter;
import org.apache.juneau.assertions.*;
/**
* Simplified logger for intercepting and asserting logging messages.
*
* <h5 class='figure'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Instantiate a mock logger.</jc>
* MockLogger logger = <jk>new</jk> MockLogger();
*
* <jc>// Associate it with a MockRestClient.</jc>
* MockRestClient
* .<jsm>create</jsm>(MyRestResource.<jk>class</jk>)
* .simpleJson()
* .logger(logger)
* .logRequests(DetailLevel.<jsf>FULL</jsf>, Level.<jsf>SEVERE</jsf>)
* .build()
* .post(<js>"/bean"</js>, bean)
* .complete();
*
* <jc>// Assert that logging occurred.</jc>
* logger.assertLastLevel(Level.<jsf>SEVERE</jsf>);
* logger.assertLastMessage().is(
* <js>"=== HTTP Call (outgoing) ======================================================"</js>,
* <js>"=== REQUEST ==="</js>,
* <js>"POST http://localhost/bean"</js>,
* <js>"---request headers---"</js>,
* <js>" Accept: application/json+simple"</js>,
* <js>"---request entity---"</js>,
* <js>" Content-Type: application/json+simple"</js>,
* <js>"---request content---"</js>,
* <js>"{f:1}"</js>,
* <js>"=== RESPONSE ==="</js>,
* <js>"HTTP/1.1 200 "</js>,
* <js>"---response headers---"</js>,
* <js>" Content-Type: application/json"</js>,
* <js>"---response content---"</js>,
* <js>"{f:1}"</js>,
* <js>"=== END ======================================================================="</js>,
* <js>""</js>
* );
* </p>
*/
public class MockLogger extends Logger {
private static final String FORMAT_PROPERTY = "java.util.logging.SimpleFormatter.format";
private final List<LogRecord> logRecords = new ArrayList<>();
private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
private volatile Formatter formatter;
private volatile String format = "%4$s: %5$s%6$s%n";
/**
* Constructor.
*/
public MockLogger() {
super("Mock", null);
}
/**
* Creator.
*
* @return A new {@link MockLogger} object.
*/
public static MockLogger create() {
return new MockLogger();
}
@Override /* Logger */
public synchronized void log(LogRecord record) {
logRecords.add(record);
try {
baos.write(getFormatter().format(record).getBytes("UTF-8"));
} catch (Exception e) {
throw runtimeException(e);
}
}
private Formatter getFormatter() {
if (formatter == null) {
synchronized(this) {
String oldFormat = System.getProperty(FORMAT_PROPERTY);
System.setProperty(FORMAT_PROPERTY, format);
formatter = new SimpleFormatter();
if (oldFormat == null)
System.clearProperty(FORMAT_PROPERTY);
else
System.setProperty(FORMAT_PROPERTY, oldFormat);
}
}
return formatter;
}
/**
* Sets the level for this logger.
*
* @param level The new level for this logger.
* @return This object (for method chaining).
*/
public synchronized MockLogger level(Level level) {
super.setLevel(level);
return this;
}
/**
* Specifies the format for messages sent to the log file.
*
* <p>
* See {@link SimpleFormatter#format(LogRecord)} for the syntax of this string.
*
* @param format The format string.
* @return This object (for method chaining).
*/
public synchronized MockLogger format(String format) {
this.format = format;
return this;
}
/**
* Overrides the formatter to use for formatting messages.
*
* <p>
* The default uses {@link SimpleFormatter}.
*
* @param formatter The log record formatter.
* @return This object (for method chaining).
*/
public synchronized MockLogger formatter(Formatter formatter) {
this.formatter = formatter;
return this;
}
/**
* Resets this logger.
*
* @return This object (for method chaining).
*/
public synchronized MockLogger reset() {
logRecords.clear();
baos.reset();
return this;
}
/**
* Asserts that this logger was called.
*
* @return This object (for method chaining).
*/
public synchronized MockLogger assertLogged() {
if (logRecords.isEmpty())
throw new AssertionError("Message not logged");
return this;
}
/**
* Asserts that the last message was logged at the specified level.
*
* @param level The level to match against.
* @return This object (for method chaining).
*/
public synchronized MockLogger assertLastLevel(Level level) {
assertLogged();
if (last().getLevel() != level)
throw new AssertionError("Message logged at [" + last().getLevel() + "] instead of [" + level + "]");
return this;
}
/**
* Asserts that the last message matched the specified message.
*
* @return This object (for method chaining).
*/
public synchronized FluentStringAssertion<MockLogger> assertLastMessage() {
assertLogged();
return new FluentStringAssertion<>(last().getMessage(), this);
}
/**
* Asserts that the specified number of messages have been logged.
*
* @return This object (for method chaining).
*/
public synchronized FluentIntegerAssertion<MockLogger> assertRecordCount() {
return new FluentIntegerAssertion<>(logRecords.size(), this);
}
/**
* Allows you to perform fluent-style assertions on the contents of the log file.
*
* @return A new fluent-style assertion object.
*/
public synchronized FluentStringAssertion<MockLogger> assertContents() {
return new FluentStringAssertion<>(baos.toString(), this);
}
private LogRecord last() {
if (logRecords.isEmpty())
throw new AssertionError("Message not logged");
return logRecords.get(logRecords.size()-1);
}
/**
* Returns the contents of this log file as a string.
*/
@Override
public String toString() {
return baos.toString();
}
}