001/** 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved. 004 * 005 * This program and the accompanying materials are dual-licensed under 006 * either the terms of the Eclipse Public License v1.0 as published by 007 * the Eclipse Foundation 008 * 009 * or (per the licensee's choosing) 010 * 011 * under the terms of the GNU Lesser General Public License version 2.1 012 * as published by the Free Software Foundation. 013 */ 014package ch.qos.logback.core.recovery; 015 016import java.io.IOException; 017import java.io.OutputStream; 018import java.util.ArrayList; 019import java.util.List; 020 021import ch.qos.logback.core.Context; 022import ch.qos.logback.core.status.ErrorStatus; 023import ch.qos.logback.core.status.InfoStatus; 024import ch.qos.logback.core.status.Status; 025import ch.qos.logback.core.status.StatusManager; 026 027abstract public class ResilientOutputStreamBase extends OutputStream { 028 029 final static int STATUS_COUNT_LIMIT = 2 * 4; 030 031 private int noContextWarning = 0; 032 private int statusCount = 0; 033 034 private Context context; 035 private RecoveryCoordinator recoveryCoordinator; 036 037 protected OutputStream os; 038 protected boolean presumedClean = true; 039 040 List<RecoveryListener> recoveryListeners = new ArrayList<>(0); 041 042 private boolean isPresumedInError() { 043 // existence of recoveryCoordinator indicates failed state 044 return (recoveryCoordinator != null && !presumedClean); 045 } 046 047 public void addRecoveryListener(RecoveryListener listener) { 048 recoveryListeners.add(listener); 049 } 050 051 public void removeRecoveryListener(RecoveryListener listener) { 052 recoveryListeners.remove(listener); 053 } 054 055 public void write(byte b[], int off, int len) { 056 if (isPresumedInError()) { 057 if (!recoveryCoordinator.isTooSoon()) { 058 attemptRecovery(); 059 } 060 return; // return regardless of the success of the recovery attempt 061 } 062 063 try { 064 os.write(b, off, len); 065 postSuccessfulWrite(); 066 } catch (IOException e) { 067 postIOFailure(e); 068 } 069 } 070 071 @Override 072 public void write(int b) { 073 if (isPresumedInError()) { 074 if (!recoveryCoordinator.isTooSoon()) { 075 attemptRecovery(); 076 } 077 return; // return regardless of the success of the recovery attempt 078 } 079 try { 080 os.write(b); 081 postSuccessfulWrite(); 082 } catch (IOException e) { 083 postIOFailure(e); 084 } 085 } 086 087 @Override 088 public void flush() { 089 if (os != null) { 090 try { 091 os.flush(); 092 postSuccessfulWrite(); 093 } catch (IOException e) { 094 postIOFailure(e); 095 } 096 } 097 } 098 099 abstract String getDescription(); 100 101 abstract OutputStream openNewOutputStream() throws IOException; 102 103 private void postSuccessfulWrite() { 104 if (recoveryCoordinator != null) { 105 recoveryCoordinator = null; 106 statusCount = 0; 107 recoveryListeners.forEach( listener -> listener.recoveryOccured()); 108 addStatus(new InfoStatus("Recovered from IO failure on " + getDescription(), this)); 109 } 110 } 111 112 public void postIOFailure(IOException e) { 113 addStatusIfCountNotOverLimit(new ErrorStatus("IO failure while writing to " + getDescription(), this, e)); 114 presumedClean = false; 115 if (recoveryCoordinator == null) { 116 recoveryCoordinator = new RecoveryCoordinator(); 117 recoveryListeners.forEach( listener -> listener.newFailure(e)); 118 } 119 } 120 121 @Override 122 public void close() throws IOException { 123 if (os != null) { 124 os.close(); 125 } 126 } 127 128 void attemptRecovery() { 129 try { 130 close(); 131 } catch (IOException e) { 132 } 133 134 addStatusIfCountNotOverLimit( 135 new InfoStatus("Attempting to recover from IO failure on " + getDescription(), this)); 136 137 // subsequent writes must always be in append mode 138 try { 139 os = openNewOutputStream(); 140 presumedClean = true; 141 } catch (IOException e) { 142 addStatusIfCountNotOverLimit(new ErrorStatus("Failed to open " + getDescription(), this, e)); 143 } 144 } 145 146 void addStatusIfCountNotOverLimit(Status s) { 147 ++statusCount; 148 if (statusCount < STATUS_COUNT_LIMIT) { 149 addStatus(s); 150 } 151 152 if (statusCount == STATUS_COUNT_LIMIT) { 153 addStatus(s); 154 addStatus(new InfoStatus("Will supress future messages regarding " + getDescription(), this)); 155 } 156 } 157 158 public void addStatus(Status status) { 159 if (context == null) { 160 if (noContextWarning++ == 0) { 161 System.out.println("LOGBACK: No context given for " + this); 162 } 163 return; 164 } 165 StatusManager sm = context.getStatusManager(); 166 if (sm != null) { 167 sm.add(status); 168 } 169 } 170 171 public Context getContext() { 172 return context; 173 } 174 175 public void setContext(Context context) { 176 this.context = context; 177 } 178}