1
2
3
4
5
6
7
8
9
10
11
12
13
14 package ch.qos.logback.core.recovery;
15
16 import java.io.IOException;
17 import java.io.OutputStream;
18 import java.util.ArrayList;
19 import java.util.List;
20
21 import ch.qos.logback.core.Context;
22 import ch.qos.logback.core.status.ErrorStatus;
23 import ch.qos.logback.core.status.InfoStatus;
24 import ch.qos.logback.core.status.Status;
25 import ch.qos.logback.core.status.StatusManager;
26
27 abstract public class ResilientOutputStreamBase extends OutputStream {
28
29 final static int STATUS_COUNT_LIMIT = 2 * 4;
30
31 private int noContextWarning = 0;
32 private int statusCount = 0;
33
34 private Context context;
35 private RecoveryCoordinator recoveryCoordinator;
36
37 protected OutputStream os;
38 protected boolean presumedClean = true;
39
40 List<RecoveryListener> recoveryListeners = new ArrayList<>(0);
41
42 private boolean isPresumedInError() {
43
44 return (recoveryCoordinator != null && !presumedClean);
45 }
46
47 public void addRecoveryListener(RecoveryListener listener) {
48 recoveryListeners.add(listener);
49 }
50
51 public void removeRecoveryListener(RecoveryListener listener) {
52 recoveryListeners.remove(listener);
53 }
54
55 public void write(byte b[], int off, int len) {
56 if (isPresumedInError()) {
57 if (!recoveryCoordinator.isTooSoon()) {
58 attemptRecovery();
59 }
60 return;
61 }
62
63 try {
64 os.write(b, off, len);
65 postSuccessfulWrite();
66 } catch (IOException e) {
67 postIOFailure(e);
68 }
69 }
70
71 @Override
72 public void write(int b) {
73 if (isPresumedInError()) {
74 if (!recoveryCoordinator.isTooSoon()) {
75 attemptRecovery();
76 }
77 return;
78 }
79 try {
80 os.write(b);
81 postSuccessfulWrite();
82 } catch (IOException e) {
83 postIOFailure(e);
84 }
85 }
86
87 @Override
88 public void flush() {
89 if (os != null) {
90 try {
91 os.flush();
92 postSuccessfulWrite();
93 } catch (IOException e) {
94 postIOFailure(e);
95 }
96 }
97 }
98
99 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
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 }