Let's say I have simple code:
public static void Main(){
throw new NullPointerException("this is an npe");
}
How with slf4j do you make sure that that exception is logged to the log file. Is there a default setting in spring that you can set in application properties, or in the logback file, that makes sure this gets captured, even though it is not in the try catch?
Please note, I am NOT looking for a solution to a webcontroller, or how to use log.error(). I know how to do those. I am looking for an overarching setting so that we don't lose exceptions throughout the application. This can apply to OOM, or printing a heap dump etc. I am including spring as a tag just in case the solution is in the applications.properties, as this is a spring project.
Ok as you have said, you can add a controller advice:
#ControllerAdvice
public class YourCustomHandler extends ResponseEntityExceptionHandler {
#Autowired
MessageSource messageSource;
#Override
protected ResponseEntity<Object> handleMissingServletRequestParameter(MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
// you can do logs here and pass a custom object that will parse to json object
return buildResponseEntity(apiError);
}
#ExceptionHandler(YourCustomException.class)
protected ResponseEntity<Object> handleTthException(
YourCustomException ex, Locale locale) {
return buildResponseEntity(apiError);
}
private ResponseEntity<Object> buildResponseEntity(ApiError apiError) {
return new ResponseEntity<>(apiError, apiError.getStatus());
}
}
Based off of this: https://sysgears.com/articles/how-to-redirect-stdout-and-stderr-writing-to-a-log4j-appender/, I have found something I believe works.
At beginning of psvm:
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
System.setErr(new PrintStream(new LoggingOutputStream(), true));
}
This says to print errors to my LoggingOutputStream.
Code for this class:
public class LoggingOutputStream extends OutputStream {
/**
* Default number of bytes in the buffer.
*/
private static final int DEFAULT_BUFFER_LENGTH = 2048;
/**
* Indicates stream state.
*/
private boolean hasBeenClosed = false;
/**
* Internal buffer where data is stored.
*/
private byte[] buf;
/**
* The number of valid bytes in the buffer.
*/
private int count;
/**
* Remembers the size of the buffer.
*/
private int curBufLength;
/**
* The logger to write to.
*/
private Logger log = LoggerFactory.getLogger(LoggingOutputStream.class);
public LoggingOutputStream(){
curBufLength = DEFAULT_BUFFER_LENGTH;
buf = new byte[curBufLength];
count = 0;
}
/**
*
* Writes the specified byte to this output stream.
*
* #param b the byte to write
* #throws IOException if an I/O error occurs.
*/
public void write(final int b) throws IOException {
if (hasBeenClosed) {
log.error("The stream has been closed.");
throw new IOException("The stream has been closed.");
}
// don't log nulls
if (b == 0) {
return;
}
// would this be writing past the buffer?
if (count == curBufLength) {
// grow the buffer
final int newBufLength = curBufLength +
DEFAULT_BUFFER_LENGTH;
final byte[] newBuf = new byte[newBufLength];
System.arraycopy(buf, 0, newBuf, 0, curBufLength);
buf = newBuf;
curBufLength = newBufLength;
}
buf[count] = (byte) b;
count++;
}
/**
* Flushes this output stream and forces any buffered output
* bytes to be written out.
*/
#Override
public void flush() {
if (count == 0) {
return;
}
final byte[] bytes = new byte[count];
System.arraycopy(buf, 0, bytes, 0, count);
String str = new String(bytes);
log.error(str);
count = 0;
}
/**
* Closes this output stream and releases any system resources
* associated with this stream.
*/
#Override
public void close() {
flush();
hasBeenClosed = true;
}
}
Now, all errors are "intercepted" and routed to my log file.
Related
I have a UDP client and server that uses DatagramChannel to join multicast group. The client is expected to be able to send strings as well as packets that contains data that is encoded by simple binary encoding.
On the other hand, in the server I need to decode it accordingly. I now am able to decode without receiving garbage values, however, currently when I send string literals as a message, it is interpreted and decoded as a MoldUdpHeader message.
For encoding in the client side:
/**
* simple helper method to send a string message to the multicast address
* #param message
* #throws IOException
*/
public void sendMessage(String message) throws IOException {
byteBuffer.clear();
byteBuffer.put(message.getBytes());
byteBuffer.flip();
client.send(byteBuffer, groupAddress);
}
/**
* simple helper method to send an encodable message to the multicast address
* #param encodableMessage
* #throws IOException
*/
public void sendMessage(IsEncodable encodableMessage) throws IOException {
int offset = 0;
encodableMessage.encode(byteBuffer, offset);
client.send(byteBuffer, groupAddress);
}
The code I use to receive data from a channel:
private boolean receiveData(DatagramChannel channel) throws IOException {
byteBuffer.clear();
SocketAddress remoteAdd = channel.receive(byteBuffer);
byteBuffer.flip();
String msg = decodeByteBuffer(byteBuffer);
System.out.println("Client at " + remoteAdd + " sent: " + msg);
return (msg.equals("end")) ? true : false;
}
This calls the method to decode byte buffer:
private String decodeByteBuffer(ByteBuffer byteBuffer){
String msg = "";
int offset = 0;
moldUdpHeader = (MoldUdpHeader) moldUdpHeader.decode(byteBuffer, offset);
msg = moldUdpHeader.toString();
System.out.println("String decoding: "+decodeStringLiteral(byteBuffer));
// appender.writeDocument(w->w.write().text("write().text() Document: "+msg));
return msg;
}
Decoding strings:
private String decodeStringLiteral(ByteBuffer byteBuffer){
int limits = byteBuffer.limit();
byte[] bytes = new byte[limits];
byteBuffer.get(bytes, 0, limits);
return new String(bytes);
}
The MoldUdpHeader is a custom class I made to store the results from the decoder stub. It basically contains the MessageHeaderEncoder and decoder from sbe-tool as well as the relevant encoder/decoder for the MoldUdpHeader message schema.
public class MoldUdpHeader implements IsEncodable, IsDecodable {
public static final MessageHeaderEncoder MESSAGE_HEADER_ENCODER;
public static final MessageHeaderDecoder MESSAGE_HEADER_DECODER;
public static final MoldUdpHeaderEncoder UDP_HEADER_ENCODER;
public static final MoldUdpHeaderDecoder UDP_HEADER_DECODER;
private long seqNum;
private long tvNsec;
private int senderId;
private int msgCnt;
static {
MESSAGE_HEADER_ENCODER = new MessageHeaderEncoder();
MESSAGE_HEADER_DECODER = new MessageHeaderDecoder();
UDP_HEADER_ENCODER = new MoldUdpHeaderEncoder();
UDP_HEADER_DECODER = new MoldUdpHeaderDecoder();
}
public MoldUdpHeader() {}
public MoldUdpHeader(long seqNum, long tvNsec, int senderId, int msgCnt) {
this.seqNum = seqNum;
this.tvNsec = tvNsec;
this.senderId = senderId;
this.msgCnt = msgCnt;
}
#Override
public IsDecodable decode(ByteBuffer byteBuffer, int offset) {
UnsafeBuffer buffer = new UnsafeBuffer(byteBuffer);
MoldUdpHeaderDecoder decoder = MOLD_UDP_HEADER_DECODER.wrap(buffer, offset
,MoldUdpHeaderDecoder.BLOCK_LENGTH, 0);
this.setSeqNum(decoder.seqNum());
this.setTvNsec(decoder.tvNsec());
this.setSenderId(decoder.senderId());
this.setMsgCnt(decoder.msgCnt());
// System.out.println(this);
return this;
}
#Override
public void encode(ByteBuffer byteBuffer, int offset) {
UnsafeBuffer buffer = new UnsafeBuffer(byteBuffer);
MOLD_UDP_HEADER_ENCODER.wrap(buffer, offset);
MOLD_UDP_HEADER_ENCODER.seqNum(this.seqNum).tvNsec(this.tvNsec).senderId(this.senderId).msgCnt(this.msgCnt);
}
//getters and setters
}
How can I ensure that the string is not malformed, or how can I choose the proper way to decode prior to anything? There is no error thrown for bad decoding, or failure to decode.
sample code for codec:
MoldUdpHeaderEncoder UDP_HEADER_ENCODER = new MoldUdpHeaderEncoder(); //sbe tool generated stub
MoldUdpHeaderDecoder UDP_HEADER_DECODER = new MoldUdpHeaderDecoder(); //sbe tool generated stub
ByteBuffer buffer = ByteBuffer.allocate(1500000000);
for (int i=0; i<=100; i++){
MoldUdpHeader msg = new MoldUdpHeader(seq++, LocalTime.now().toSecondOfDay()* 1_000_000_000L,0xDEADBEEF, i );
System.out.println(msg);
UnsafeBuffer bb = new UnsafeBuffer(buffer);
UDP_HEADER_ENCODER.wrap(bb, 0);
UDP_HEADER_ENCODER.seqNum(msg.getSeqNum()).tvNsec(msg.getTvNsec()).senderId(msg.getSenderId()).msgCnt(msg.getMsgCnt());
MoldUdpHeaderDecoder decoder = UDP_HEADER_DECODER.wrap(bb, 0
,MoldUdpHeaderDecoder.BLOCK_LENGTH, 0);
System.out.println("decoded: "+decoder.seqNum()+", "+decoder.tvNsec()+", "+decoder.senderId()+", "+decoder.msgCnt());
if (i==100){ //example of getting a random string message
bb.wrap("end".getBytes());
decoder = UDP_HEADER_DECODER.wrap(bb, 0
,MoldUdpHeaderDecoder.BLOCK_LENGTH, 0);
System.out.println("decoded: "+decoder.seqNum()+", "+decoder.tvNsec()+", "+decoder.senderId()+", "+decoder.msgCnt());
}
}
Output:
I have spring mvc application using RequestBody and ResponseBody annotations. They are configured with MappingJackson2HttpMessageConverter. I also have slf4j set up. I would like to log all json as it comes in and out from my controller.
I did extend
MappingJackson2HttpMessageConverter
#Override
public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
logStream(inputMessage.getBody());
return super.read(type, contextClass, inputMessage);
}
I can get the input stream, but if I read the content it becomes empty and I loose the message. Moreover mark() and reset() is not supported. It is implemented by PushbackInputStream, so I tried to read it's content and push it back like this:
public void logStream(InputStream is) {
if (is instanceof PushbackInputStream)
try {
PushbackInputStream pushbackInputStream = (PushbackInputStream) is;
byte[] bytes = new byte[20000];
StringBuilder sb = new StringBuilder(is.available());
int red = is.read();
int pos =0;
while (red > -1) {
bytes[pos] = (byte) red;
pos=1 + pos;
red = is.read();
}
pushbackInputStream.unread(bytes,0, pos-1);
log.info("Json payload " + sb.toString());
} catch (Exception e) {
log.error("ignoring exception in logger ", e);
}
}
but I get exception
java.io.IOException: Push back buffer is full
I also tried to turn on logging on http level as described here:Spring RestTemplate - how to enable full debugging/logging of requests/responses? without luck.
After more than whole work day of experimenting I got working solution.
It consists of Logging filter, two wrappers for request and response and registration of Logging filter:
the filter class is:
/**
* Http logging filter, which wraps around request and response in
* each http call and logs
* whole request and response bodies. It is enabled by
* putting this instance into filter chain
* by overriding getServletFilters() in
* AbstractAnnotationConfigDispatcherServletInitializer.
*/
public class LoggingFilter extends AbstractRequestLoggingFilter {
private static final Logger log = LoggerFactory.getLogger(LoggingFilter.class);
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
long id = System.currentTimeMillis();
RequestLoggingWrapper requestLoggingWrapper = new RequestLoggingWrapper(id, request);
ResponseLoggingWrapper responseLoggingWrapper = new ResponseLoggingWrapper(id, response);
log.debug(id + ": http request " + request.getRequestURI());
super.doFilterInternal(requestLoggingWrapper, responseLoggingWrapper, filterChain);
log.debug(id + ": http response " + response.getStatus() + " finished in " + (System.currentTimeMillis() - id) + "ms");
}
#Override
protected void beforeRequest(HttpServletRequest request, String message) {
}
#Override
protected void afterRequest(HttpServletRequest request, String message) {
}
}
this class is using stream wrappers, which was suggested by
Master Slave and David Ehrmann.
Request wrapper looks like this:
/**
* Request logging wrapper using proxy split stream to extract request body
*/
public class RequestLoggingWrapper extends HttpServletRequestWrapper {
private static final Logger log = LoggerFactory.getLogger(RequestLoggingWrapper.class);
private final ByteArrayOutputStream bos = new ByteArrayOutputStream();
private long id;
/**
* #param requestId and id which gets logged to output file. It's used to bind request with
* response
* #param request request from which we want to extract post data
*/
public RequestLoggingWrapper(Long requestId, HttpServletRequest request) {
super(request);
this.id = requestId;
}
#Override
public ServletInputStream getInputStream() throws IOException {
final ServletInputStream servletInputStream = RequestLoggingWrapper.super.getInputStream();
return new ServletInputStream() {
private TeeInputStream tee = new TeeInputStream(servletInputStream, bos);
#Override
public int read() throws IOException {
return tee.read();
}
#Override
public int read(byte[] b, int off, int len) throws IOException {
return tee.read(b, off, len);
}
#Override
public int read(byte[] b) throws IOException {
return tee.read(b);
}
#Override
public boolean isFinished() {
return servletInputStream.isFinished();
}
#Override
public boolean isReady() {
return servletInputStream.isReady();
}
#Override
public void setReadListener(ReadListener readListener) {
servletInputStream.setReadListener(readListener);
}
#Override
public void close() throws IOException {
super.close();
// do the logging
logRequest();
}
};
}
public void logRequest() {
log.info(getId() + ": http request " + new String(toByteArray()));
}
public byte[] toByteArray() {
return bos.toByteArray();
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
}
and response wrapper is different only in close/flush method (close doesn't get called)
public class ResponseLoggingWrapper extends HttpServletResponseWrapper {
private static final Logger log = LoggerFactory.getLogger(ResponseLoggingWrapper.class);
private final ByteArrayOutputStream bos = new ByteArrayOutputStream();
private long id;
/**
* #param requestId and id which gets logged to output file. It's used to bind response with
* response (they will have same id, currenttimemilis is used)
* #param response response from which we want to extract stream data
*/
public ResponseLoggingWrapper(Long requestId, HttpServletResponse response) {
super(response);
this.id = requestId;
}
#Override
public ServletOutputStream getOutputStream() throws IOException {
final ServletOutputStream servletOutputStream = ResponseLoggingWrapper.super.getOutputStream();
return new ServletOutputStream() {
private TeeOutputStream tee = new TeeOutputStream(servletOutputStream, bos);
#Override
public void write(byte[] b) throws IOException {
tee.write(b);
}
#Override
public void write(byte[] b, int off, int len) throws IOException {
tee.write(b, off, len);
}
#Override
public void flush() throws IOException {
tee.flush();
logRequest();
}
#Override
public void write(int b) throws IOException {
tee.write(b);
}
#Override
public boolean isReady() {
return servletOutputStream.isReady();
}
#Override
public void setWriteListener(WriteListener writeListener) {
servletOutputStream.setWriteListener(writeListener);
}
#Override
public void close() throws IOException {
super.close();
// do the logging
logRequest();
}
};
}
public void logRequest() {
byte[] toLog = toByteArray();
if (toLog != null && toLog.length > 0)
log.info(getId() + ": http response " + new String(toLog));
}
/**
* this method will clear the buffer, so
*
* #return captured bytes from stream
*/
public byte[] toByteArray() {
byte[] ret = bos.toByteArray();
bos.reset();
return ret;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
}
at last LoggingFilter needs to be registered in AbstractAnnotationConfigDispatcherServletInitializer like this:
#Override
protected Filter[] getServletFilters() {
LoggingFilter requestLoggingFilter = new LoggingFilter();
return new Filter[]{requestLoggingFilter};
}
I know, there is maven lib for this, but I don't want to include whole lib because of small logging utility. It was much harder than I originally thought. I expected to achieve this just by modifying log4j.properties. I still think this should be part of Spring.
It sounds like you want to decorate HttpInputMessage so it returns a decorated InputStream that logs all reads in an internal buffer, then on close() or finalize() logs what was read.
Here's an InputStream that will capture what was read:
public class LoggingInputStream extends FilterInputStream {
private ByteArrayOutputStream out = new ByteArrayOutputStream();
private boolean logged = false;
protected LoggingInputStream(InputStream in) {
super(in);
}
#Override
protected void finalize() throws Throwable {
try {
this.log();
} finally {
super.finalize();
}
}
#Override
public void close() throws IOException {
try {
this.log();
} finally {
super.close();
}
}
#Override
public int read() throws IOException {
int r = super.read();
if (r >= 0) {
out.write(r);
}
return r;
}
#Override
public int read(byte[] b) throws IOException {
int read = super.read(b);
if (read > 0) {
out.write(b, 0, read);
}
return read;
}
#Override
public int read(byte[] b, int off, int len) throws IOException {
int read = super.read(b, off, len);
if (read > 0) {
out.write(b, off, read);
}
return read;
}
#Override
public long skip(long n) throws IOException {
long skipped = 0;
byte[] b = new byte[4096];
int read;
while ((read = this.read(b, 0, (int)Math.min(n, b.length))) >= 0) {
skipped += read;
n -= read;
}
return skipped;
}
private void log() {
if (!logged) {
logged = true;
try {
log.info("Json payload " + new String(out.toByteArray(), "UTF-8");
} catch (UnsupportedEncodingException e) { }
}
}
}
And now
#Override
public Object read(Type type, Class<?> contextClass, final HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
return super.read(type, contextClass, new HttpInputMessage() {
#Override
public InputStream getBody() {
return new LoggingInputStream(inputMessage.getBody());
}
#Override
public HttpHeaders getHeaders() {
return inputMessage.getHeaders();
}
});
}
Decorating HttpInputMessage as David Ehrmann suggested is one likely solution.
The whole trouble with this functionality is that it requires InputStream to be read more than once. However, this is not possible, once you read a portion or a stream, its "consumed" and there no way to go back and read it again.
A typical solution is to apply a filter that will create a wrapper for a request that will allow re-reads of the inputStream. One approach is by using the TeeInputStream which copies all the bytes read from InputStream to a secondary OutputStream.
There's a github project that uses just that kind of a filter and in fact just for the same purpose spring-mvc-logger The RequestWrapper class used
public class RequestWrapper extends HttpServletRequestWrapper {
private final ByteArrayOutputStream bos = new ByteArrayOutputStream();
private long id;
public RequestWrapper(Long requestId, HttpServletRequest request) {
super(request);
this.id = requestId;
}
#Override
public ServletInputStream getInputStream() throws IOException {
return new ServletInputStream() {
private TeeInputStream tee = new TeeInputStream(RequestWrapper.super.getInputStream(), bos);
#Override
public int read() throws IOException {
return tee.read();
}
};
}
public byte[] toByteArray(){
return bos.toByteArray();
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
}
A similar implementation wraps the response as well
How can I convert an OutputStream to a byte array? I have found that first I need to convert this OutputStream to a ByteArrayOutputStream. There is only write() method in this OutputStream class and I don't know what to do. Is there any idea?
Create a ByteArrayOutputStream.
Grab its content by calling toByteArray()
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.writeTo(myOutputStream);
baos.toByteArray();
Reference
If the OutputStream object supplied is not already a ByteArrayOutputStream, one can wrap it inside a delegate class that will "grab" the bytes supplied to the write() methods, e.g.
public class DrainableOutputStream extends FilterOutputStream {
private final ByteArrayOutputStream buffer;
public DrainableOutputStream(OutputStream out) {
super(out);
this.buffer = new ByteArrayOutputStream();
}
#Override
public void write(byte b[]) throws IOException {
this.buffer.write(b);
super.write(b);
}
#Override
public void write(byte b[], int off, int len) throws IOException {
this.buffer.write(b, off, len);
super.write(b, off, len);
}
#Override
public void write(int b) throws IOException {
this.buffer.write(b);
super.write(b);
}
public byte[] toByteArray() {
return this.buffer.toByteArray();
}
}
To reduce the overhead, the calls to super in the above class can be omitted - e.g., if only the "conversion" to a byte array is desired.
A more detailed discussion can be found in another StackOverflow question.
You need to do 2 things
Using ByteArrayOutputStream write to it
Using toByteArray(), you will get the contents as byte[]
You could even extend it as mentioned here
You could simply declare your output stream as a ByteArrayOutputStream then use ByteArrayOutputStream#toByteArray().
If You try ByteArrayOutputStream bos=(ByteArrayOutputStream)outputStream then throw ClassCastException.
I did it when I get OutputStream from HttpServletResponse and it is CoyoteOutputStream.(You can create file so dont create file temp).
You can use Java Reflection to convert OutputStream to byte[]:
byte[] bytes = this.getBytes(outStream);
/**
* Get byte from OutputStream
*
* #param outStream
* #return
*/
#SneakyThrows
private byte[] getBytes(OutputStream outStream) {
OutputBuffer outputBuffer = (OutputBuffer) this.getValueByName("ob", outStream);
ByteBuffer byteBuffer = (ByteBuffer) this.getValueByName("bb", outputBuffer);
return (byte[]) this.getValueByName("hb", byteBuffer);
}
/**
* Get value from property
*
* #param name
* #param value
* #return
*/
#SneakyThrows
#SuppressWarnings("unchecked")
private Object getValueByName(String name, Object value) {
List<Field> listFiled = new ArrayList<>();
if (value.getClass().getSuperclass() != null) {
listFiled.addAll(Arrays.asList(value.getClass().getSuperclass().getDeclaredFields()));
}
listFiled.addAll(Arrays.asList(value.getClass().getDeclaredFields()));
Optional<Field> fieldOb = listFiled.stream()
.filter(field -> StringUtils.equalsAnyIgnoreCase(name, field.getName()))
.findFirst();
if (fieldOb.isPresent()) {
Field field = fieldOb.get();
field.setAccessible(true);
return field.get(value);
}
return StringUtils.EMPTY; // FIXME
}
In my project test suite there is big usage of
System.out.println
I'm trying to redirect these output to log file (through configuration or from single point without refactoring whole project ) so that can be disabled when necessary to improve performance. I'm using log4j for logging.
Does any one know is this possible ? if so how to do it ?
Thanks in advance.
Given that it's better replace the System.out.println(), sometimes we have no choice. Anyway I've made a little utility for that:
SystemOutToSlf4j.enableForClass(MyClass.class)
Then all the println originated from MyClass will be redirected to the logger.
See this post for more details...
public class SystemOutToSlf4j extends PrintStream {
private static final PrintStream originalSystemOut = System.out;
private static SystemOutToSlf4j systemOutToLogger;
/**
* Enable forwarding System.out.println calls to the logger if the stacktrace contains the class parameter
* #param clazz
*/
public static void enableForClass(Class clazz) {
systemOutToLogger = new SystemOutToSlf4j(originalSystemOut, clazz.getName());
System.setOut(systemOutToLogger);
}
/**
* Enable forwarding System.out.println calls to the logger if the stacktrace contains the package parameter
* #param packageToLog
*/
public static void enableForPackage(String packageToLog) {
systemOutToLogger = new SystemOutToSlf4j(originalSystemOut, packageToLog);
System.setOut(systemOutToLogger);
}
/**
* Disable forwarding to the logger resetting the standard output to the console
*/
public static void disable() {
System.setOut(originalSystemOut);
systemOutToLogger = null;
}
private String packageOrClassToLog;
private SystemOutToSlf4j(PrintStream original, String packageOrClassToLog) {
super(original);
this.packageOrClassToLog = packageOrClassToLog;
}
#Override
public void println(String line) {
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
StackTraceElement caller = findCallerToLog(stack);
if (caller == null) {
super.println(line);
return;
}
org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(caller.getClass());
log.info(line);
}
public StackTraceElement findCallerToLog(StackTraceElement[] stack) {
for (StackTraceElement element : stack) {
if (element.getClassName().startsWith(packageOrClassToLog))
return element;
}
return null;
}
}
My suggestion would be to refactor if possible.
For a possible solution, check these similar questions
log4j redirect stdout to DailyRollingFileAppender
Redirect System.out.println to Log4J, while keeping class name information
I think you can use System.setOut(PrintStream) to set your output to a file output stream. Then you can put this line in your BeforeClass method. I like to use a BaseTest class and put this line of code in the beforeclass method of that class. Then make all test cases extend this cclass.
Use shell redirection. Figure out the "java" invocation for your project, if you're on most vaguely UNIX-like systems, ps aux | grep java will help.
Then just run this command with > /path/to/logfile. Example:
java -jar myjar.jar -cp path/to/lib.jar:path/to/otherlib.jar com.bigcorp.myproject.Main > /var/log/myproject.log
public class RecursiveLogging {
public static void main(String[] args) {
System.setOut(new PrintStream(new CustomOutputStream()));
TestMyException.testPrint();
}
}
class CustomOutputStream extends OutputStream {
private Logger logger = Logger.getLogger(this.getClass());
#Override
public final void write(int b) throws IOException {
// the correct way of doing this would be using a buffer
// to store characters until a newline is encountered,
// this implementation is for illustration only
logger.info((char) b);
}
#Override
public void write(byte[] b, int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
byte[] pb = new byte[len];
for (int i = 0 ; i < len ; i++) {
pb[i] = (b[off + i]);
}
String str = new String(pb);
logger.info(str);
}
}
My solution is pretty simple and supports all PrintStream functionality without overloading everything. overloading only flush() as it called by PrintStream methods every new line.
public class ConsoleToLogger
{
private Logger log;
private PrintStream originalStream;
private Level logLevel;
private ByteArrayBufferOutStream buffer;
private PrintStream bufferPrintStream;
ConsoleToLogger(PrintStream realPrintStream, Level pLogLevel)
{
buffer = new ByteArrayBufferOutStream();
bufferPrintStream = new PrintStream(buffer);
originalStream = realPrintStream;
logLevel = pLogLevel;
log = Logger.getLogger(Level.ERROR.equals(pLogLevel) ? "STDERR" : "STDOUT");
}
public PrintStream getPrintStream()
{
return bufferPrintStream;
}
private class ByteArrayBufferOutStream
extends ByteArrayOutputStream
{
#Override
public void flush()
throws IOException
{
super.flush();
String message = buffer.toString();
originalStream.println(message);
log.log(logLevel, message);
buffer.reset();
}
}
}
// assign to System.out and system.err
System.setOut(new ConsoleToLogger(System.out, Level.INFO).getPrintStream());
System.setErr(new ConsoleToLogger(System.err, Level.ERROR).getPrintStream());
I'm working on a music player, which receives a playlist with remote mp3 files (HTTP) and play them subsequently.
I want to have it start streaming the first track, if enough of the song is buffered to play it through, it should already begin to buffer the following song into memory. That is to make up for the unstable internet connection the program is supposed to run on.
How do I tell the BufferedInputStream to just download the whole file?
I'm happy to hear other suggestions on how to solve this, too.
I'm using the JLayer/BasicPlayer library to play audio, this is the code.
String mp3Url = "http://ia600402.us.archive.org/6/items/Stockfinster.-DeadLinesutemos025/01_Push_Push.mp3";
URL url = new URL(mp3Url);
URLConnection conn = url.openConnection();
InputStream is = conn.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
BasicPlayer player = new BasicPlayer();
player.open(bis);
player.play();
Here you will get an example for how to prebuffer audio file in java
Ok, to answer my question, here's a working implementation:
/**
* <code>DownloaderInputStream</code>
*/
public class DownloaderInputStream extends InputStream {
/**
* <code>IDownloadNotifier</code> - download listener.
*/
public static interface IDownloadListener {
/**
* Notifies about download completion.
*
* #param buf
* #param offset
* #param length
*/
public void onComplete(final byte[] buf, final int offset, final int length);
}
/**
* <code>ByteArrayOutputStreamX</code> - {#link ByteArrayOutputStream}
* extension that exposes buf variable (to avoid copying).
*/
private final class ByteArrayOutputStreamX extends ByteArrayOutputStream {
/**
* Constructor.
*
* #param size
*/
public ByteArrayOutputStreamX(final int size) {
super(size);
}
/**
* Returns inner buffer.
*
* #return inner buffer
*/
public byte[] getBuffer() {
return buf;
}
}
private final class Downloader extends Object implements Runnable {
// fields
private final InputStream is;
/**
* Constructor.
*
* #param is
*/
public Downloader(final InputStream is) {
this.is = is;
}
// Runnable implementation
public void run() {
int read = 0;
byte[] buf = new byte[16 * 1024];
try {
while ((read = is.read(buf)) != -1) {
if (read > 0) {
content.write(buf, 0, read);
downloadedBytes += read;
} else {
Thread.sleep(50);
}
}
} catch (Exception e) {
e.printStackTrace();
}
listener.onComplete(content.getBuffer(), 0 /*
* offset
*/, downloadedBytes);
}
}
// fields
private final int contentLength;
private final IDownloadListener listener;
// state
private ByteArrayOutputStreamX content;
private volatile int downloadedBytes;
private volatile int readBytes;
/**
* Constructor.
*
* #param contentLength
* #param is
* #param listener
*/
public DownloaderInputStream(final int contentLength, final InputStream is, final IDownloadListener listener) {
this.contentLength = contentLength;
this.listener = listener;
this.content = new ByteArrayOutputStreamX(contentLength);
this.downloadedBytes = 0;
this.readBytes = 0;
new Thread(new Downloader(is)).start();
}
/**
* Returns number of downloaded bytes.
*
* #return number of downloaded bytes
*/
public int getDownloadedBytes() {
return downloadedBytes;
}
/**
* Returns number of read bytes.
*
* #return number of read bytes
*/
public int getReadBytes() {
return readBytes;
}
// InputStream implementation
#Override
public int available() throws IOException {
return downloadedBytes - readBytes;
}
#Override
public int read() throws IOException {
// not implemented (not necessary for BasicPlayer)
return 0;
}
#Override
public int read(byte[] b, int off, int len) throws IOException {
if (readBytes == contentLength) {
return -1;
}
int tr = 0;
while ((tr = Math.min(downloadedBytes - readBytes, len)) == 0) {
try {
Thread.sleep(100);
} catch (Exception e) {/*
* ignore
*/
}
}
byte[] buf = content.getBuffer();
System.arraycopy(buf, readBytes, b, off, tr);
readBytes += tr;
return tr;
}
#Override
public long skip(long n) throws IOException {
// not implemented (not necessary for BasicPlayer)
return n;
}
}