net.oneandone.stool.util.Logging Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of main Show documentation
Show all versions of main Show documentation
Stool's main component. Java Library, cli, setup code.
/**
* 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";
public 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.TIME_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;
}
}