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;
018
019import ch.qos.logback.core.Context;
020import ch.qos.logback.core.status.ErrorStatus;
021import ch.qos.logback.core.status.InfoStatus;
022import ch.qos.logback.core.status.Status;
023import ch.qos.logback.core.status.StatusManager;
024
025abstract public class ResilientOutputStreamBase extends OutputStream {
026
027    final static int STATUS_COUNT_LIMIT = 2 * 4;
028
029    private int noContextWarning = 0;
030    private int statusCount = 0;
031
032    private Context context;
033    private RecoveryCoordinator recoveryCoordinator;
034
035    protected OutputStream os;
036    protected boolean presumedClean = true;
037
038    private boolean isPresumedInError() {
039        // existence of recoveryCoordinator indicates failed state
040        return (recoveryCoordinator != null && !presumedClean);
041    }
042
043    public void write(byte b[], int off, int len) {
044        if (isPresumedInError()) {
045            if (!recoveryCoordinator.isTooSoon()) {
046                attemptRecovery();
047            }
048            return; // return regardless of the success of the recovery attempt
049        }
050
051        try {
052            os.write(b, off, len);
053            postSuccessfulWrite();
054        } catch (IOException e) {
055            postIOFailure(e);
056        }
057    }
058
059    @Override
060    public void write(int b) {
061        if (isPresumedInError()) {
062            if (!recoveryCoordinator.isTooSoon()) {
063                attemptRecovery();
064            }
065            return; // return regardless of the success of the recovery attempt
066        }
067        try {
068            os.write(b);
069            postSuccessfulWrite();
070        } catch (IOException e) {
071            postIOFailure(e);
072        }
073    }
074
075    @Override
076    public void flush() {
077        if (os != null) {
078            try {
079                os.flush();
080                postSuccessfulWrite();
081            } catch (IOException e) {
082                postIOFailure(e);
083            }
084        }
085    }
086
087    abstract String getDescription();
088
089    abstract OutputStream openNewOutputStream() throws IOException;
090
091    private void postSuccessfulWrite() {
092        if (recoveryCoordinator != null) {
093            recoveryCoordinator = null;
094            statusCount = 0;
095            addStatus(new InfoStatus("Recovered from IO failure on " + getDescription(), this));
096        }
097    }
098
099    public void postIOFailure(IOException e) {
100        addStatusIfCountNotOverLimit(new ErrorStatus("IO failure while writing to " + getDescription(), this, e));
101        presumedClean = false;
102        if (recoveryCoordinator == null) {
103            recoveryCoordinator = new RecoveryCoordinator();
104        }
105    }
106
107    @Override
108    public void close() throws IOException {
109        if (os != null) {
110            os.close();
111        }
112    }
113
114    void attemptRecovery() {
115        try {
116            close();
117        } catch (IOException e) {
118        }
119
120        addStatusIfCountNotOverLimit(new InfoStatus("Attempting to recover from IO failure on " + getDescription(), this));
121
122        // subsequent writes must always be in append mode
123        try {
124            os = openNewOutputStream();
125            presumedClean = true;
126        } catch (IOException e) {
127            addStatusIfCountNotOverLimit(new ErrorStatus("Failed to open " + getDescription(), this, e));
128        }
129    }
130
131    void addStatusIfCountNotOverLimit(Status s) {
132        ++statusCount;
133        if (statusCount < STATUS_COUNT_LIMIT) {
134            addStatus(s);
135        }
136
137        if (statusCount == STATUS_COUNT_LIMIT) {
138            addStatus(s);
139            addStatus(new InfoStatus("Will supress future messages regarding " + getDescription(), this));
140        }
141    }
142
143    public void addStatus(Status status) {
144        if (context == null) {
145            if (noContextWarning++ == 0) {
146                System.out.println("LOGBACK: No context given for " + this);
147            }
148            return;
149        }
150        StatusManager sm = context.getStatusManager();
151        if (sm != null) {
152            sm.add(status);
153        }
154    }
155
156    public Context getContext() {
157        return context;
158    }
159
160    public void setContext(Context context) {
161        this.context = context;
162    }
163}