
net.oneandone.stool.util.Logging Maven / Gradle / Ivy
/**
* Copyright 1&1 Internet AG, https://github.com/1and1/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.oneandone.stool.util;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.encoder.Encoder;
import ch.qos.logback.core.encoder.EncoderBase;
import ch.qos.logback.core.rolling.RollingFileAppender;
import ch.qos.logback.core.rolling.TimeBasedRollingPolicy;
import net.oneandone.sushi.fs.Settings;
import net.oneandone.sushi.fs.file.FileNode;
import net.oneandone.sushi.io.MultiOutputStream;
import net.oneandone.sushi.io.PrefixWriter;
import net.oneandone.sushi.util.Strings;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Logging {
private static final String EXTENSION = ".log";
private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyMMdd-");
public static Logging forStool(FileNode lib, String user) throws IOException {
return create(lib.join("logs"), "stool", user);
}
public static Logging create(FileNode dir, String name, String user) throws IOException {
String today;
String id;
Logging result;
today = DATE_FORMAT.format(LocalDate.now());
id = today + Integer.toString(id(dir, today));
result = new Logging(id, dir.join(name + EXTENSION), user);
result.configureRootLogger();
return result;
}
public final String id;
private final LoggerContext context;
private final FileNode file;
private final String user;
private String stageId;
private String stageName;
public Logging(String id, FileNode file, String user) throws IOException {
this.id = id;
this.context = (LoggerContext) LoggerFactory.getILoggerFactory();
this.file = file;
this.user = user;
setStage("", "");
if (!file.exists()) {
file.writeBytes();
Files.stoolFile(file);
}
}
public void setStage(String id, String name) {
stageId = id;
stageName = name;
}
private void configureRootLogger() {
Logger root;
// adjust the default configuration
root = context.getLogger("ROOT");
root.detachAndStopAllAppenders();
root.addAppender(appender("OTHER"));
root.setLevel(Level.INFO);
}
public PrintWriter writer(OutputStream stream, String logger) {
PrintWriter result;
try {
result = new PrintWriter(
new OutputStreamWriter(MultiOutputStream.createTeeStream(stream, new Slf4jOutputStream(logger(logger), false)), Settings.UTF_8), true);
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException(e);
}
// empty prefix is replaced by stage commands when iterating multiple stages:
result = new PrefixWriter(result);
return result;
}
public Logger logger(String name) {
Logger result;
result = context.getLogger(name);
// important for test cases, where you might instantiate multiple Logging objects
result.detachAndStopAllAppenders();
result.setAdditive(false);
result.setLevel(Level.INFO);
result.addAppender(appender(name));
return result;
}
private RollingFileAppender appender(String logger) {
RollingFileAppender result;
TimeBasedRollingPolicy policy;
result = new RollingFileAppender() {
@Deprecated
public void rollover() {
super.rollover();
try {
if (!file.exists()) {
file.mkfile();
}
// Make sure the file is always group-writable, because all users share the same log file
// (The archived log is not a problems, because it's written exactly one, all later access is reading)
Files.stoolFile(file);
} catch (IOException e) {
throw new RuntimeException("TODO", e);
}
}
};
result.setContext(context);
result.setName(logger + "-appender");
result.setEncoder(encoder(logger));
result.setAppend(true);
result.setFile(file.getAbsolute());
policy = new TimeBasedRollingPolicy();
policy.setContext(context);
policy.setParent(result);
policy.setFileNamePattern(file.getParent().getAbsolute() + "/" + Strings.removeRightOpt(file.getName(), EXTENSION)
+ "-%d{yyyy-MM-dd}.log.gz");
policy.setMaxHistory(180);
policy.start();
result.setRollingPolicy(policy);
result.start();
return result;
}
private Encoder encoder(final String logger) {
return new EncoderBase() {
private PrintWriter writer;
@Override
public void init(OutputStream out) {
try {
writer = new PrintWriter(new OutputStreamWriter(out, Settings.UTF_8));
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException(e);
}
}
@Override
public void doEncode(ILoggingEvent event) throws IOException {
String message;
char c;
writer.append(LogEntry.FMT.format(LocalDateTime.now())).append('|');
writer.append(id).append('|');
writer.append(logger).append('|');
writer.append(user).append('|');
writer.append(stageId).append('|');
writer.append(stageName).append('|');
message = event.getFormattedMessage();
for (int i = 0, max = message.length(); i < max; i++) {
c = message.charAt(i);
switch (c) {
case '\r':
writer.append("\\r");
break;
case '\n':
writer.append("\\n");
break;
case '\\':
writer.append("\\\\");
break;
default:
writer.append(c);
break;
}
}
writer.append('\n');
writer.flush();
}
@Override
public void close() throws IOException {
if (writer != null) {
writer.close();
}
}
};
}
//--
/**
* Unique id starting with 1 every day, bumped for every invocation.
*/
private static int id(FileNode varRun, String prefix) throws IOException {
int retries;
FileNode lock;
FileNode file;
int id;
String str;
retries = 0;
while (true) {
lock = varRun.join("id.lock");
try {
lock.mkfile();
break;
} catch (IOException e) {
retries++;
if (retries > 10) {
throw new IOException("cannot create " + lock);
}
try {
Thread.sleep(100);
} catch (InterruptedException e1) {
break;
}
}
}
try {
file = varRun.join("id");
if (!file.exists()) {
id = 1;
touch(file);
} else {
str = file.readString();
if (str.startsWith(prefix)) {
id = Integer.parseInt(str.substring(prefix.length())) + 1;
} else {
id = 1;
}
}
file.writeString(prefix + id);
return id;
} finally {
lock.deleteFile();
}
}
private static FileNode touch(FileNode file) throws IOException {
if (!file.exists()) {
file.mkfile();
file.setPermissions("rw-rw----");
}
return file;
}
private FileNode directory() {
return file.getParent();
}
/** @return alle COMMAND Log entries operating on the specified stage */
public List stageCommands(String stageId) throws Exception {
LogEntry entry;
Map commands;
LogEntry command;
List result;
result = new ArrayList<>();
commands = new HashMap<>();
try (LogReader reader = LogReader.create(directory())) {
while (true) {
entry = reader.next();
if (entry == null) {
break;
}
if (entry.logger.equals("COMMAND")) {
if (commands.put(entry.id, entry) != null) {
throw new IllegalStateException("duplicate id: " + entry.id);
}
}
if (entry.stageId.equals(stageId)) {
command = commands.remove(entry.id);
if (command != null) {
result.add(command);
}
}
}
}
return result;
}
public List info(String stageId, String id) throws Exception {
LogEntry entry;
List result;
result = new ArrayList<>();
try (LogReader reader = LogReader.create(directory())) {
while (true) {
entry = reader.next();
if (entry == null) {
break;
}
if (entry.id.equals(id) && entry.stageId.equals(stageId)) {
result.add(entry);
}
}
}
return result;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy