1 /*
2 * Copyright 2012 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package org.openehealth.ipf.commons.ihe.core.payload;
17
18 import org.apache.commons.io.FileUtils;
19 import org.apache.commons.io.IOUtils;
20 import org.slf4j.Logger;
21 import org.slf4j.LoggerFactory;
22
23 import java.io.*;
24 import java.util.concurrent.atomic.AtomicInteger;
25 import java.util.concurrent.atomic.AtomicLong;
26 import java.util.stream.Collectors;
27 import java.util.stream.Stream;
28
29 import static java.util.Objects.requireNonNull;
30
31
32 /**
33 * Base class for interceptors which store incoming and outgoing payload
34 * into files with user-defined name patterns, or to the regular Java log.
35 * <p>
36 * File name patterns can contain absolute and relative paths and must correspond to the
37 * <a href="http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/expressions.html">SpEL</a>
38 * syntax, using square brackets for referencing placeholder parameters.
39 * In the base version, the following parameters are supported
40 * (this set can be extended in derived classes):
41 * <ul>
42 * <li><tt>sequenceId</tt> — internally generated sequential ID
43 * as a 12-digit positive long int, zero-padded.</li>
44 * <li><tt>processId</tt> — process ID consisting from the OS process
45 * number and the host name, e.g. <tt>"12345-myhostname"</tt>.</li>
46 * <li><tt>date('format_spec')</tt> — current date and time, formatted
47 * using {@link java.text.SimpleDateFormat} according to the given specification.</li>
48 * </ul>
49 * <br>
50 * Example of a file name pattern:<br>
51 * <tt>C:/IPF-LOGS/[processId]/[date('yyyyMMdd-HH00')]/[sequenceId]-server-output.txt</tt>
52 * <br>
53 * After a pre-configured count of failed trials to create a file, the logger will be switched off.
54 * <p>
55 * As an alternative to SpEL, the user can provide another {@link ExpressionResolver expression resolver}.
56 * <p>
57 * Furthermore, the behavior of this class is regulated application-widely by the following Boolean
58 * system properties:
59 * <ul>
60 * <li><tt>org.openehealth.ipf.commons.ihe.core.payload.PayloadLoggerBase.CONSOLE</tt> —
61 * when set to <code>true</code>, then the message payload will be logged using regular
62 * Java logging mechanisms (level DEBUG) instead of being written into files whose names
63 * are created from the pattern.</li>
64 * <li><tt>org.openehealth.ipf.commons.ihe.core.payload.PayloadLoggerBase.DISABLED</tt> —
65 * when set to <code>true</code>, then no logging will be performed at all.</li>
66 * </ul>
67 *
68 * @author Dmytro Rud
69 */
70 abstract public class PayloadLoggerBase<T extends PayloadLoggingContext> {
71 private static final transient Logger LOG = LoggerFactory.getLogger(PayloadLoggerBase.class);
72
73 private static final AtomicLong SEQUENCE_ID_GENERATOR = new AtomicLong(0L);
74
75 // CXF message property
76 public static final String SEQUENCE_ID_PROPERTY_NAME =
77 PayloadLoggerBase.class.getName() + ".sequence.id";
78
79 // Java system properties
80 public static final String PROPERTY_CONSOLE = PayloadLoggerBase.class.getName() + ".CONSOLE";
81 public static final String PROPERTY_DISABLED = PayloadLoggerBase.class.getName() + ".DISABLED";
82
83 private boolean enabled = true;
84
85 private int errorCountLimit = -1;
86 private AtomicInteger errorCount = new AtomicInteger(0);
87
88 private ExpressionResolver resolver;
89
90 protected static Long getNextSequenceId() {
91 return SEQUENCE_ID_GENERATOR.getAndIncrement();
92 }
93
94 protected void doLogPayload(T context, String charsetName, String... payloadPieces) {
95 // check whether we can process
96 if (!canProcess()) {
97 return;
98 }
99 if ((errorCountLimit >= 0) && (errorCount.get() >= errorCountLimit)) {
100 LOG.warn("Error count limit has bean reached, reset the counter to enable further trials");
101 return;
102 }
103
104 if (Boolean.getBoolean(PROPERTY_CONSOLE)) {
105 // use regular Java logging
106 if (LOG.isDebugEnabled()) {
107 String output = Stream.of(payloadPieces).collect(Collectors.joining());
108 LOG.debug(output);
109 }
110 } else {
111 // compute the file path and write payload pieces into this file
112 String path = resolver.resolveExpression(context);
113 Writer writer = null;
114 try {
115 FileOutputStream outputStream = FileUtils.openOutputStream(new File(path), true);
116 writer = (charsetName != null) ?
117 new OutputStreamWriter(outputStream, charsetName) :
118 new OutputStreamWriter(outputStream);
119 for (String payloadPiece : payloadPieces) {
120 writer.write(payloadPiece);
121 }
122 errorCount.set(0);
123 } catch (IOException e) {
124 errorCount.incrementAndGet();
125 LOG.warn("Cannot write into " + path, e);
126 } finally {
127 IOUtils.closeQuietly(writer);
128 }
129 }
130 }
131
132
133 public boolean canProcess() {
134 if ((!enabled) || Boolean.getBoolean(PROPERTY_DISABLED)) {
135 LOG.trace("Message payload logging is disabled");
136 return false;
137 }
138 return true;
139 }
140
141
142 /**
143 * Resets count of occurred errors, can be used e.g. via JMX.
144 */
145 public void resetErrorCount() {
146 errorCount.set(0);
147 }
148
149 /**
150 * @return <code>true</code> if this logging interceptor instance is enabled.
151 */
152 public boolean isEnabled() {
153 return enabled;
154 }
155
156 /**
157 * @param enabled <code>true</code> when this logging interceptor instance should be enabled.
158 */
159 public void setEnabled(boolean enabled) {
160 this.enabled = enabled;
161 }
162
163 /**
164 * @return <code>true</code> if this logging interceptor instance is enabled.
165 * @deprecated use {@link #isEnabled()}
166 */
167 @Deprecated
168 public boolean isLocallyEnabled() {
169 return isEnabled();
170 }
171
172 /**
173 * @param locallyEnabled <code>true</code> when this logging interceptor instance should be enabled.
174 * @deprecated use {@link #setEnabled(boolean)}
175 */
176 @Deprecated
177 public void setLocallyEnabled(boolean locallyEnabled) {
178 setEnabled(locallyEnabled);
179 }
180
181 /**
182 * @return <code>true</code> when logging interceptors are generally enabled.
183 * @see #isLocallyEnabled()
184 * @deprecated use environment variable {@link #PROPERTY_DISABLED}
185 */
186 public static boolean isGloballyEnabled() {
187 return !Boolean.getBoolean(PROPERTY_DISABLED);
188 }
189
190 /**
191 * @param globallyEnabled <code>true</code> when logging interceptors shall be generally enabled.
192 * @see #setLocallyEnabled(boolean)
193 * @deprecated use environment variable {@link #PROPERTY_DISABLED}
194 */
195 public static void setGloballyEnabled(boolean globallyEnabled) {
196 System.setProperty(PROPERTY_DISABLED, Boolean.toString(!globallyEnabled));
197 }
198
199 /**
200 * @return maximal allowed count of file creation errors,
201 * negative value (the default) means "no limit".
202 */
203 public int getErrorCountLimit() {
204 return errorCountLimit;
205 }
206
207 /**
208 * Configures maximal allowed count of file creation errors.
209 *
210 * @param errorCountLimit maximal allowed count of file creation errors,
211 * negative value (the default) means "no limit".
212 */
213 public void setErrorCountLimit(int errorCountLimit) {
214 this.errorCountLimit = errorCountLimit;
215 }
216
217 public ExpressionResolver getExpressionResolver() {
218 return resolver;
219 }
220
221 public void setExpressionResolver(ExpressionResolver resolver) {
222 this.resolver = requireNonNull(resolver);
223 }
224 }