public AsyncRequestLog( FileSystemAbstraction fs, ZoneId logTimeZone, String logFile, long rotationSize, int rotationKeepNumber ) throws IOException { NamedThreadFactory threadFactory = new NamedThreadFactory( "HTTP-Log-Rotator", true ); ExecutorService rotationExecutor = Executors.newCachedThreadPool( threadFactory ); outputSupplier = new RotatingFileOutputStreamSupplier( fs, new File( logFile ), rotationSize, 0, rotationKeepNumber, rotationExecutor ); FormattedLogProvider logProvider = FormattedLogProvider.withZoneId( logTimeZone ) .toOutputStream( outputSupplier ); asyncLogProcessingExecutor = Executors.newSingleThreadExecutor( new NamedThreadFactory( "HTTP-Log-Writer" ) ); asyncEventProcessor = new AsyncEvents<>( this, this ); AsyncLogProvider asyncLogProvider = new AsyncLogProvider( asyncEventProcessor, logProvider ); log = asyncLogProvider.getLog( "REQUEST" ); }
@Test void shouldCloseAllOutputStreams() throws Exception { final List<OutputStream> mockStreams = new ArrayList<>(); FileSystemAbstraction fs = new DelegatingFileSystemAbstraction( fileSystem ) { @Override public OutputStream openAsOutputStream( File fileName, boolean append ) throws IOException { final OutputStream stream = spy( super.openAsOutputStream( fileName, append ) ); mockStreams.add( stream ); return stream; } }; RotatingFileOutputStreamSupplier supplier = new RotatingFileOutputStreamSupplier( fs, logFile, 10, 0, 10, DIRECT_EXECUTOR ); write( supplier, "A string longer than 10 bytes" ); supplier.close(); assertStreamClosed( mockStreams.get( 0 ) ); }
@Test void shouldFindAllArchives() throws Exception { RotatingFileOutputStreamSupplier supplier = new RotatingFileOutputStreamSupplier( fileSystem, logFile, 10, 0, 2, DIRECT_EXECUTOR ); write( supplier, "A string longer than 10 bytes" ); write( supplier, "A string longer than 10 bytes" ); assertThat( fileSystem.fileExists( logFile ), is( true ) ); assertThat( fileSystem.fileExists( archiveLogFile1 ), is( true ) ); assertThat( fileSystem.fileExists( archiveLogFile2 ), is( false ) ); List<File> allArchives = getAllArchives( fileSystem, logFile ); assertThat( allArchives.size(), is( 1 ) ); assertThat( allArchives, hasItem( archiveLogFile1 ) ); }
/** * @return A stream outputting to the latest output file */ @Override public OutputStream get() { if ( !closed.get() && !rotating.get() ) { // In case output file doesn't exist, call rotate so that it gets created if ( rotationDelayExceeded() && rotationThresholdExceeded() || !fileSystem.fileExists( outputFile ) ) { rotate(); } } return this.streamWrapper; }
private void shiftArchivedOutputFiles() throws IOException { for ( int i = lastArchivedOutputFileNumber( fileSystem, outputFile ); i > 0; --i ) { File archive = archivedOutputFile( outputFile, i ); if ( i >= maxArchives ) { fileSystem.deleteFile( archive ); } else { fileSystem.renameFile( archive, archivedOutputFile( outputFile, i + 1 ) ); } } }
@Test void shouldNotifyListenerOnRotationErrorDuringJobExecution() throws Exception { RotationListener rotationListener = mock( RotationListener.class ); Executor executor = mock( Executor.class ); RotatingFileOutputStreamSupplier supplier = new RotatingFileOutputStreamSupplier( fileSystem, logFile, 10, 0, 10, executor, rotationListener ); OutputStream outputStream = supplier.get(); RejectedExecutionException exception = new RejectedExecutionException( "text exception" ); doThrow( exception ).when( executor ).execute( any( Runnable.class ) ); write( supplier, "A string longer than 10 bytes" ); assertThat( supplier.get(), is( outputStream ) ); verify( rotationListener ).rotationError( exception, outputStream ); }
try RotatingFileOutputStreamSupplier supplier = new RotatingFileOutputStreamSupplier( fs, logFile, 10, 0, 10, rotationExecutor, rotationListener ); OutputStream outputStream = supplier.get(); assertThat( supplier.get(), is( outputStream ) ); supplier.close();
DefaultFileSystemAbstraction defaultFileSystemAbstraction = new DefaultFileSystemAbstraction(); RotatingFileOutputStreamSupplier supplier = new RotatingFileOutputStreamSupplier( defaultFileSystemAbstraction, logFile, 0, 0, 10, executor, listener ); OutputStream outputStream = supplier.get(); LockingPrintWriter lockingPrintWriter = new LockingPrintWriter( outputStream ); lockingPrintWriter.withLock( () -> supplier.rotate(); latch.await(); return Void.TYPE;
@Override protected synchronized void doStop() throws IOException { asyncEventProcessor.shutdown(); asyncEventProcessor.awaitTermination(); outputSupplier.close(); }
shiftArchivedOutputFiles(); fileSystem.renameFile( outputFile, archivedOutputFile( outputFile, 1 ) ); outRef = openOutputFile(); rotationListener.outputFileCreated( bufferingOutputStream );
/** * This is to be used by loggers that uses {@link RotatingFileOutputStreamSupplier}. * * @param destination final destination in archive. * @param fs filesystem abstraction to use. * @param file input log file, should be without rotation numbers. * @return a list diagnostics sources consisting of the log file including all rotated away files. */ public static List<DiagnosticsReportSource> newDiagnosticsRotatingFile( String destination, FileSystemAbstraction fs, File file ) { ArrayList<DiagnosticsReportSource> files = new ArrayList<>(); files.add( newDiagnosticsFile( destination, fs, file ) ); List<File> allArchives = getAllArchives( fs, file ); for ( File archive : allArchives ) { String name = archive.getName(); String n = name.substring( name.lastIndexOf( '.' ) ); files.add( newDiagnosticsFile( destination + "." + n, fs, archive ) ); } return files; }
private static int lastArchivedOutputFileNumber( FileSystemAbstraction fileSystem, File outputFile ) { int i = 1; while ( fileSystem.fileExists( archivedOutputFile( outputFile, i ) ) ) { i++; } return i - 1; }
private void write( RotatingFileOutputStreamSupplier supplier, String line ) { PrintWriter writer = new PrintWriter( supplier.get() ); writer.println( line ); writer.flush(); }
@Test void shouldReattemptRotationAfterExceptionDuringJobExecution() throws Exception { RotationListener rotationListener = mock( RotationListener.class ); Executor executor = mock( Executor.class ); RotatingFileOutputStreamSupplier supplier = new RotatingFileOutputStreamSupplier( fileSystem, logFile, 10, 0, 10, executor, rotationListener ); OutputStream outputStream = supplier.get(); RejectedExecutionException exception = new RejectedExecutionException( "text exception" ); doThrow( exception ).when( executor ).execute( any( Runnable.class ) ); write( supplier, "A string longer than 10 bytes" ); assertThat( supplier.get(), is( outputStream ) ); assertThat( supplier.get(), is( outputStream ) ); verify( rotationListener, times( 2 ) ).rotationError( exception, outputStream ); }
/** * @return A stream outputting to the latest output file */ @Override public OutputStream get() { if ( !closed.get() && !rotating.get() ) { // In case output file doesn't exist, call rotate so that it gets created if ( rotationDelayExceeded() && rotationThresholdExceeded() || !fileSystem.fileExists( outputFile ) ) { rotate(); } } return this.streamWrapper; }
@Override public void shutdown() throws Throwable { if ( this.rotatingSupplier != null ) { this.rotatingSupplier.close(); this.rotatingSupplier = null; } } }
shiftArchivedOutputFiles(); fileSystem.renameFile( outputFile, archivedOutputFile( outputFile, 1 ) ); outRef = openOutputFile(); rotationListener.outputFileCreated( bufferingOutputStream );
private void shiftArchivedOutputFiles() throws IOException { for ( int i = lastArchivedOutputFileNumber( fileSystem, outputFile ); i > 0; --i ) { File archive = archivedOutputFile( outputFile, i ); if ( i >= maxArchives ) { fileSystem.deleteFile( archive ); } else { fileSystem.renameFile( archive, archivedOutputFile( outputFile, i + 1 ) ); } } }
/** * This is to be used by loggers that uses {@link RotatingFileOutputStreamSupplier}. * * @param destination final destination in archive. * @param fs filesystem abstraction to use. * @param file input log file, should be without rotation numbers. * @return a list diagnostics sources consisting of the log file including all rotated away files. */ public static List<DiagnosticsReportSource> newDiagnosticsRotatingFile( String destination, FileSystemAbstraction fs, File file ) { ArrayList<DiagnosticsReportSource> files = new ArrayList<>(); files.add( newDiagnosticsFile( destination, fs, file ) ); List<File> allArchives = getAllArchives( fs, file ); for ( File archive : allArchives ) { String name = archive.getName(); String n = name.substring( name.lastIndexOf( '.' ) ); files.add( newDiagnosticsFile( destination + "." + n, fs, archive ) ); } return files; }
/** * Exposes the algorithm for collecting existing rotated log files. */ public static List<File> getAllArchives( FileSystemAbstraction fileSystem, File outputFile ) { ArrayList<File> ret = new ArrayList<>(); int i = 1; while ( true ) { File file = archivedOutputFile( outputFile, i ); if ( !fileSystem.fileExists( file ) ) { break; } ret.add( file ); i++; } return ret; } }