View Javadoc
1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
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          // existence of recoveryCoordinator indicates failed state
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; // return regardless of the success of the recovery attempt
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; // return regardless of the success of the recovery attempt
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         // 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 }