org.tinylog.pattern.FormatPatternParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tinylog-impl Show documentation
Show all versions of tinylog-impl Show documentation
tinylog native logging implementation
The newest version!
/*
* Copyright 2016 Martin Winandy
*
* 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 org.tinylog.pattern;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
import org.tinylog.Level;
import org.tinylog.configuration.ServiceLoader;
import org.tinylog.provider.InternalLogger;
import org.tinylog.throwable.ThrowableFilter;
/**
* Parser for format patterns. It produces tokens combined to a root token that can be used by writers for rendering log
* entries.
*/
public final class FormatPatternParser {
private static final Pattern SPLIT_PATTERN = Pattern.compile(",");
private final List filters;
/**
* @param filters
* Comma separated list of throwable filters
*/
public FormatPatternParser(final String filters) {
if (filters == null) {
this.filters = Collections.emptyList();
} else {
this.filters = new ServiceLoader(ThrowableFilter.class, String.class).createList(filters);
}
}
/**
* Parses a format pattern and produces a generic token from it. This token can be used by writers for rendering log
* entries.
*
* @param pattern
* Format pattern
* @return Produced root token
*/
public Token parse(final String pattern) {
List tokens = new ArrayList();
int start = 0;
int count = 0;
for (int i = 0; i < pattern.length(); ++i) {
char character = pattern.charAt(i);
if (character == '{') {
if (count == 0) {
if (start < i) {
tokens.add(new PlainTextToken(pattern.substring(start, i)));
}
start = i;
}
count += 1;
} else if (character == '}') {
if (count == 0) {
InternalLogger.log(Level.ERROR, "Opening curly bracket is missing: '" + pattern + "'");
} else {
count -= 1;
if (count == 0) {
tokens.add(parse(pattern.substring(start + 1, i)));
start = i + 1;
}
}
}
}
if (count > 0) {
InternalLogger.log(Level.ERROR, "Closing curly bracket is missing: '" + pattern + "'");
}
int splitIndex = pattern.indexOf('|', start);
if (splitIndex == -1) {
tokens.add(createPlainToken(pattern.substring(start)));
return tokens.size() == 1 ? tokens.get(0) : new BundleToken(tokens);
} else {
String token = pattern.substring(start, splitIndex).trim();
tokens.add(createPlainToken(token));
String[] styleOptions = SPLIT_PATTERN.split(pattern.substring(splitIndex + 1));
return styleToken(tokens.size() == 1 ? tokens.get(0) : new BundleToken(tokens), styleOptions);
}
}
/**
* Creates a new token for a given placeholder.
*
* @param placeholder
* Placeholder without style options and surrounding curly brackets
* @return Created token
*/
private Token createPlainToken(final String placeholder) {
int splitIndex = placeholder.indexOf(':');
Token token;
if (splitIndex == -1) {
token = createPlainToken(placeholder.trim(), null);
} else {
String name = placeholder.substring(0, splitIndex).trim();
String configuration = placeholder.substring(splitIndex + 1).trim();
token = createPlainToken(name, configuration);
}
return token == null ? new PlainTextToken(placeholder) : token;
}
/**
* Creates a new token for a name and configuration.
*
* @param name
* Name of token
* @param configuration
* Configuration for token or {@code null} if not defined
* @return Created token or {@code null} if there is no token for the passed parameters
*/
private Token createPlainToken(final String name, final String configuration) {
if (name.equals("date")) {
return createDateToken(configuration);
} else if ("timestamp".equals(name)) {
return new TimestampToken(configuration);
} else if ("uptime".equals(name)) {
return configuration == null ? new UptimeToken() : new UptimeToken(configuration);
} else if ("pid".equals(name)) {
return new ProcessIdToken();
} else if ("thread".equals(name)) {
return new ThreadNameToken();
} else if ("thread-id".equals(name)) {
return new ThreadIdToken();
} else if ("context".equals(name)) {
return createThreadContextToken(configuration);
} else if ("class".equals(name)) {
return new FullClassNameToken();
} else if ("class-name".equals(name)) {
return new SimpleClassNameToken();
} else if ("package".equals(name)) {
return new PackageNameToken();
} else if ("method".equals(name)) {
return new MethodNameToken();
} else if ("file".equals(name)) {
return new FileNameToken();
} else if ("line".equals(name)) {
return new LineNumberToken();
} else if ("tag".equals(name)) {
return configuration == null ? new LoggerTagToken() : new LoggerTagToken(configuration);
} else if ("level".equals(name)) {
return new SeverityLevelToken();
} else if ("level-code".equals(name)) {
return new SeverityLevelIntegerToken();
} else if ("message".equals(name)) {
return new MessageAndExceptionToken(filters);
} else if ("message-only".equals(name)) {
return new MessageToken();
} else if ("exception".equals(name)) {
return new ExceptionToken(filters);
} else if ("opening-curly-bracket".equals(name)) {
return new PlainTextToken("{");
} else if ("closing-curly-bracket".equals(name)) {
return new PlainTextToken("}");
} else if ("pipe".equals(name)) {
return new PlainTextToken("|");
} else {
return null;
}
}
/**
* Creates a new {@link DateToken}.
*
* @param configuration
* Defined format pattern or {@code null} for default pattern
* @return New instance of {@link DateToken}
*/
private static Token createDateToken(final String configuration) {
if (configuration == null) {
return new DateToken();
} else {
try {
return new DateToken(configuration);
} catch (IllegalArgumentException ex) {
InternalLogger.log(Level.ERROR, "'" + configuration + "' is an invalid date format pattern");
return new DateToken();
}
}
}
/**
* Creates a new {@link ThreadContextToken}.
*
* @param configuration
* Key and optional placeholder for empty values
* @return New instance of {@link ThreadContextToken} or {@code null} if key is missing
*/
private static Token createThreadContextToken(final String configuration) {
if (configuration == null) {
return new ThreadContextToken();
} else {
int splitIndex = configuration.indexOf(',');
String key = splitIndex == -1 ? configuration.trim() : configuration.substring(0, splitIndex).trim();
if (key.isEmpty() && splitIndex != -1) {
InternalLogger.log(Level.ERROR, "\"{context}\" requires a key if a default value is supplied");
return new PlainTextToken("");
} else {
String defaultValue = splitIndex == -1 ? null : configuration.substring(splitIndex + 1).trim();
return defaultValue == null ? new ThreadContextToken(key) : new ThreadContextToken(key, defaultValue);
}
}
}
/**
* Creates style decorators for a token.
*
* @param token
* Token to style
* @param options
* Style options
* @return Styled token
*/
private static Token styleToken(final Token token, final String[] options) {
Token styledToken = token;
for (String option : options) {
int splitIndex = option.indexOf('=');
if (splitIndex == -1) {
InternalLogger.log(Level.ERROR, "No value set for '" + option.trim() + "'");
} else {
String key = option.substring(0, splitIndex).trim();
String value = option.substring(splitIndex + 1).trim();
int number;
try {
number = parsePositiveInteger(value);
} catch (NumberFormatException ex) {
InternalLogger.log(Level.ERROR, "'" + value + "' is an invalid value for '" + key + "'");
continue;
}
if ("min-size".equals(key)) {
styledToken = new MinimumSizeToken(styledToken, number);
} else if ("max-size".equals(key)) {
styledToken = new MaximumSizeToken(styledToken, number);
} else if ("size".equals(key)) {
styledToken = new SizeToken(styledToken, number);
} else if ("indent".equals(key)) {
styledToken = new IndentationToken(styledToken, number);
} else {
InternalLogger.log(Level.ERROR, "Unknown style option: '" + key + "'");
}
}
}
return styledToken;
}
/**
* Parses a positive integer. In opposite to {@link Integer#parseInt(String)}, this method throws a
* {@link NumberFormatException} for negative values.
*
* @param value
* Number as text
* @return Parsed integer
* @throws NumberFormatException
* Text doesn't contain a valid positive integer
*/
private static int parsePositiveInteger(final String value) throws NumberFormatException {
int number = Integer.parseInt(value);
if (number >= 0) {
return number;
} else {
throw new NumberFormatException();
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy