Merge pull request #62 from tkowalcz/56-patterns-in-labels

56 patterns in labels
This commit is contained in:
Tomasz Kowalczewski authored and GitHub committed 2021-07-10 07:41:02 +02:00
commit 0b5f98da18
19 files changed
+466 -57

No files matched your search

@@ -32,19 +32,25 @@ public class LabelSerializer {
return this;
}
public void startAppendingLabelValue() {
public LabelSerializer startAppendingLabelValue() {
lastSizePosition = cursor;
cursor += Integer.BYTES;
return this;
}
public void appendPartialLabelValue(String value) {
cursor += buffer.putStringWithoutLengthAscii(cursor, value);
public LabelSerializer appendPartialLabelValue(CharSequence value) {
cursor += buffer.putStringWithoutLengthAscii(cursor, value.toString());
return this;
}
public void finishAppendingLabelValue() {
public LabelSerializer finishAppendingLabelValue() {
buffer.putInt(
lastSizePosition,
cursor - lastSizePosition - Integer.BYTES);
return this;
}
public LabelSerializer appendLabel(String key, String value) {
@@ -64,20 +64,19 @@ class LabelSerializerTest {
// When
serializer.appendLabel("Foo", "Bar");
serializer.appendLabelName("Aliens");
serializer.startAppendingLabelValue();
serializer.appendPartialLabelValue("Kang");
serializer.appendPartialLabelValue("Kodos");
serializer.appendPartialLabelValue("Johnson");
serializer.finishAppendingLabelValue();
serializer.appendLabelName("Omicronians");
serializer.startAppendingLabelValue();
serializer.appendPartialLabelValue("Lrrr");
serializer.appendPartialLabelValue("RULER OF THE PLANET OMICRON PERSEI EIGHT");
serializer.finishAppendingLabelValue();
serializer.appendLabel("Popplers", "Problem");
serializer
.appendLabelName("Aliens")
.startAppendingLabelValue()
.appendPartialLabelValue("Kang")
.appendPartialLabelValue("Kodos")
.appendPartialLabelValue("Johnson")
.finishAppendingLabelValue()
.appendLabelName("Omicronians")
.startAppendingLabelValue()
.appendPartialLabelValue("Lrrr")
.appendPartialLabelValue("RULER OF THE PLANET OMICRON PERSEI EIGHT")
.finishAppendingLabelValue()
.appendLabel("Popplers", "Problem");
// Then
assertThat(serializer.getLabelsCount()).isEqualTo(4);
+27 -1
View File
@@ -30,6 +30,7 @@ Log4j2 appender seemed like a good first.
1. Include minimal appender configuration:
```xml
<Loki name="loki-appender">
<host>${sys:loki.host}</host>
<port>${sys:loki.port}</port>
@@ -45,6 +46,7 @@ Log4j2 appender seemed like a good first.
1. Reference the appender from inside one of your logger definitions:
```xml
<Root level="INFO">
<AppenderRef ref="Loki"/>
</Root>
@@ -59,6 +61,7 @@ will configure the appender to call to URL: `http://loki.mydomain.com:3100/loki/
### Grafana Cloud configuration
Tjahzi can send logs to Grafana Cloud. It needs two things to be configured:
- Set port number to `443` which switches HTTP client into HTTPS mode.
- Specify `username` and `password` for HTTP basic authentication that Grafana uses.
@@ -66,6 +69,7 @@ Password is your "Grafana.com API Key" and can be generated in "Grafana datasour
is just for illustrative purposes.
```xml
<Loki name="loki-appender">
<!-- example host -->
<host>logs-prod-us-central1.grafana.net</host>
@@ -115,12 +119,27 @@ attribute of configuration so that the appender can be found.
Contents of the properties
are [automatically interpolated by Log4j2](https://logging.apache.org/log4j/log4j-2.2/manual/configuration.html#PropertySubstitution)
. All environment, system etc. variable references will be replaced by their values during initialization of the
appender. The exception is context/MDC (`${ctx:foo}`) value lookup as it make sense only during runtime.
appender. The exception to this rule is context/MDC (`${ctx:foo}`) value lookup - it is performed for each message at
runtime (allocation free).
NOTE: This process could have been executed for every lookup type at runtime (for each log message). This approach was
deemed too expensive. If you need a mechanism to replace a variable (other than context/MDC) after logging system
initialization I would love to hear your use case - please file an issue.
## Patterns in Labels
Alternative way of specifying label contents is via pattern attribute:
```xml
<Label name="server" pattern="%C{1.}"/>
```
This pattern is compatible with
Log4j [pattern layout](https://logging.apache.org/log4j/2.x/manual/layouts.html#PatternLayout). In fact, we reuse lgo4j
internal classes for this implementation. It is generally efficient and allocation free as
per [documentation](https://logging.apache.org/log4j/log4j-2.12.1/manual/garbagefree.html#PatternLayout).
## Details
Let's go through the example config above and analyze configuration options (**Note: Tags are case-insensitive**).
@@ -154,6 +173,13 @@ when [running Loki in multi-tenant mode](https://grafana.com/docs/loki/latest/op
Specify additional labels attached to each log line sent via this appender instance. See also note
about [label naming](https://github.com/tkowalcz/tjahzi/wiki/Label-naming).
You can use value attribute to specify static text. You can use `${}` variable substitution inside that text and Tjahzi
will resolve variables once at startup. If the varaible is a context/MDC lookup it will be resolved dynamically for each
log line.
This tag also supports `pattern` attribute where you can use pattern layout expressions that will be resolved at
runtime.
#### LogLevelLabel (optional)
If defined then log level label of configured name will be added to each line sent to Loki. It will contain Log4j log
@@ -122,7 +122,12 @@ public class LokiAppenderBuilder<B extends LokiAppenderBuilder<B>> extends Abstr
);
}
LabelFactory labelFactory = new LabelFactory(logLevelLabel, labels);
LabelFactory labelFactory = new LabelFactory(
getConfiguration(),
logLevelLabel,
labels
);
LabelsDescriptor labelsDescriptor = labelFactory.convertLabelsDroppingInvalid();
logLevelLabel = labelsDescriptor.getLogLevelLabel();
@@ -18,7 +18,7 @@ public class AggregateLabelPrinter implements LabelPrinter {
}
@Override
public void append(LogEvent event, Consumer<String> appendable) {
public void append(LogEvent event, Consumer<CharSequence> appendable) {
for (LabelPrinter node : nodes) {
node.append(event, appendable);
}
@@ -7,37 +7,69 @@ import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.config.plugins.PluginValue;
import org.apache.logging.log4j.status.StatusLogger;
import pl.tkowalcz.tjahzi.log4j2.Property;
import java.util.regex.Pattern;
@Plugin(name = "label", category = Node.CATEGORY, printObject = true)
public class Label extends Property {
public class Label {
private static final Logger LOGGER = StatusLogger.getLogger();
private static final Pattern LABEL_NAME_PATTER = Pattern.compile("[a-zA-Z_][a-zA-Z0-9_]*");
private Label(String name, String value) {
super(name, value);
private final String name;
private final String value;
private final String pattern;
private Label(String name, String value, String pattern) {
this.name = name;
this.value = value;
this.pattern = pattern;
}
@PluginFactory
public static Label createLabel(
@PluginAttribute("name") String name,
@PluginValue("value") String value) {
@PluginValue("value") String value,
@PluginValue("pattern") String pattern
) {
if (name == null) {
LOGGER.error("Property name cannot be null");
}
return new Label(name, value);
if (pattern == null && value == null) {
LOGGER.error("Property must have pattern or value specified");
}
return new Label(name, value, pattern);
}
public boolean hasValidName() {
return hasValidName(getName());
}
public String getName() {
return name;
}
public String getPattern() {
return pattern;
}
public String getValue() {
return value;
}
public static boolean hasValidName(String label) {
return LABEL_NAME_PATTER.matcher(label).matches();
}
@Override
public String toString() {
return "Label{" +
"name='" + name + '\'' +
", pattern='" + pattern + '\'' +
", value='" + value + '\'' +
'}';
}
}
@@ -1,6 +1,9 @@
package pl.tkowalcz.tjahzi.log4j2.labels;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.apache.logging.log4j.core.pattern.PatternParser;
import org.apache.logging.log4j.status.StatusLogger;
import pl.tkowalcz.tjahzi.github.GitHubDocs;
@@ -22,9 +25,13 @@ public class LabelFactory {
private final String logLevelLabel;
private final Label[] labels;
public LabelFactory(String logLevelLabel, Label... labels) {
private final PatternParser patternParser;
public LabelFactory(Configuration configuration, String logLevelLabel, Label... labels) {
this.logLevelLabel = logLevelLabel;
this.labels = labels;
this.patternParser = new PatternParser(configuration, PatternLayout.KEY, null);
}
public LabelsDescriptor convertLabelsDroppingInvalid() {
@@ -95,11 +102,19 @@ public class LabelFactory {
)
.collect(toMap(
Label::getName,
LabelPrinterFactory::parse,
this::toLabelOrLog4jPattern,
(original, duplicate) -> duplicate)
);
}
private LabelPrinter toLabelOrLog4jPattern(Label label) {
if (label.getPattern() != null) {
return Log4jAdapterLabelPrinter.of(patternParser.parse(label.getPattern()));
}
return LabelPrinterFactory.parse(label);
}
private static String validateLogLevelLabel(
String logLevelLabel,
Map<String, ?> staticLabels,
@@ -7,7 +7,7 @@ import java.util.function.Consumer;
public interface LabelPrinter {
void append(LogEvent event, Consumer<String> appendable);
void append(LogEvent event, Consumer<CharSequence> appendable);
default boolean isStatic() {
return false;
@@ -22,7 +22,7 @@ public class Literal implements LabelPrinter {
}
@Override
public void append(LogEvent event, Consumer<String> appendable) {
public void append(LogEvent event, Consumer<CharSequence> appendable) {
appendable.accept(contents);
}
}
@@ -0,0 +1,53 @@
package pl.tkowalcz.tjahzi.log4j2.labels;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.pattern.LiteralPatternConverter;
import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
import org.apache.logging.log4j.core.pattern.PatternFormatter;
import java.util.List;
import java.util.function.Consumer;
@SuppressWarnings("ForLoopReplaceableByForEach")
public class Log4jAdapterLabelPrinter implements LabelPrinter {
private final StringBuilder outputBuffer = new StringBuilder();
private final List<PatternFormatter> formatters;
public Log4jAdapterLabelPrinter(List<PatternFormatter> formatters) {
this.formatters = formatters;
}
@Override
public void append(LogEvent event, Consumer<CharSequence> appendable) {
outputBuffer.setLength(0);
for (int i = 0; i < formatters.size(); i++) {
formatters.get(i).format(event, outputBuffer);
}
appendable.accept(outputBuffer);
}
@Override
public boolean isStatic() {
for (int i = 0; i < formatters.size(); i++) {
LogEventPatternConverter converter = formatters.get(i).getConverter();
if (!(converter instanceof LiteralPatternConverter)) {
return false;
}
if (((LiteralPatternConverter) converter).getLiteral().contains("$")) {
return false;
}
}
return true;
}
public static Log4jAdapterLabelPrinter of(List<PatternFormatter> parse) {
return new Log4jAdapterLabelPrinter(parse);
}
}
@@ -19,7 +19,7 @@ public class MDCLookup implements LabelPrinter {
}
@Override
public void append(LogEvent event, Consumer<String> appendable) {
public void append(LogEvent event, Consumer<CharSequence> appendable) {
Object value = event.getContextData().getValue(variableName);
if (value != null) {
appendable.accept(value.toString());
@@ -1,6 +1,9 @@
package pl.tkowalcz.tjahzi.log4j2.labels;
import org.apache.logging.log4j.core.config.NullConfiguration;
import org.apache.logging.log4j.core.config.xml.XmlConfiguration;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import java.util.Map;
@@ -11,11 +14,12 @@ class LabelFactoryTest {
@Test
void shouldRemoveDuplicatedLabels() {
// Given
Label thisIsADuplicateAndShouldBeDropped = Label.createLabel("ip", "127.0.0.1");
Label thisShouldStay = Label.createLabel("ip", "10.0.2.34");
Label thisTooShouldStay = Label.createLabel("hostname", "hostname");
Label thisIsADuplicateAndShouldBeDropped = Label.createLabel("ip", "127.0.0.1", null);
Label thisShouldStay = Label.createLabel("ip", "10.0.2.34", null);
Label thisTooShouldStay = Label.createLabel("hostname", "hostname", null);
LabelFactory labelFactory = new LabelFactory(
new NullConfiguration(),
"log_level",
thisIsADuplicateAndShouldBeDropped,
thisShouldStay,
@@ -36,17 +40,22 @@ class LabelFactoryTest {
@Test
void shouldIdentifyDynamicLabels() {
// Given
Label thisIsAStaticLabel = Label.createLabel("ip", "127.0.0.1");
Label thisIsAlsoAStaticLabel = Label.createLabel("region", "us-east-1");
Label thisIsADynamicLabel = Label.createLabel("tenant", "${ctx:tenant}");
Label thisIsAnotherDynamicLabel = Label.createLabel("tenant", "foo_${ctx:baz}_bar");
Label thisIsAStaticLabel = Label.createLabel("ip", "127.0.0.1", null);
Label thisIsAlsoAStaticLabel = Label.createLabel("region", "us-east-1", null);
Label thisIsADynamicLabel = Label.createLabel("tenant", "${ctx:tenant}", null);
Label thisIsAnotherDynamicLabel = Label.createLabel("tenant", "foo_${ctx:baz}_bar", null);
Label thisIsAStaticPatternLabel = Label.createLabel("AZ", null, "us-east-1a");
Label thisIsADynamicPatternLabel = Label.createLabel("iam_role", null, "${iam}");
LabelFactory labelFactory = new LabelFactory(
new NullConfiguration(),
"log_level",
thisIsAStaticLabel,
thisIsAlsoAStaticLabel,
thisIsADynamicLabel,
thisIsAnotherDynamicLabel
thisIsAnotherDynamicLabel,
thisIsAStaticPatternLabel,
thisIsADynamicPatternLabel
);
// When
@@ -56,24 +65,27 @@ class LabelFactoryTest {
assertThat(actual.getStaticLabels())
.containsOnly(
asEntry(thisIsAStaticLabel),
asEntry(thisIsAlsoAStaticLabel)
asEntry(thisIsAlsoAStaticLabel),
asEntryUsingPattern(thisIsAStaticPatternLabel)
);
assertThat(actual.getDynamicLabels().keySet())
.containsOnly(
thisIsADynamicLabel.getName(),
thisIsAnotherDynamicLabel.getName()
thisIsAnotherDynamicLabel.getName(),
thisIsADynamicPatternLabel.getName()
);
}
@Test
void shouldSkipLabelsWithInvalidName() {
// Given
Label thisShouldStay = Label.createLabel("ip", "10.0.2.34");
Label thisShouldStayToo = Label.createLabel("region", "coruscant-west-2");
Label invalidNameShouldBeDropped = Label.createLabel("host-name", "hostname");
Label thisShouldStay = Label.createLabel("ip", "10.0.2.34", null);
Label thisShouldStayToo = Label.createLabel("region", "coruscant-west-2", null);
Label invalidNameShouldBeDropped = Label.createLabel("host-name", "hostname", null);
LabelFactory labelFactory = new LabelFactory(
new NullConfiguration(),
"log_level",
thisShouldStayToo,
thisShouldStay,
@@ -93,10 +105,11 @@ class LabelFactoryTest {
@Test
void shouldAcceptNullLogLevel() {
// Given
Label label1 = Label.createLabel("ip", "10.0.2.34");
Label label2 = Label.createLabel("region", "coruscant-west-2");
Label label1 = Label.createLabel("ip", "10.0.2.34", null);
Label label2 = Label.createLabel("region", "coruscant-west-2", null);
LabelFactory labelFactory = new LabelFactory(
new NullConfiguration(),
null,
label2,
label1
@@ -117,10 +130,11 @@ class LabelFactoryTest {
// Given
String logLevelLabel = "log_level";
Label thisShouldStay = Label.createLabel("ip", "10.0.2.34");
Label thisShouldBeRemovedDueToConflict = Label.createLabel(logLevelLabel, "coruscant-west-2");
Label thisShouldStay = Label.createLabel("ip", "10.0.2.34", null);
Label thisShouldBeRemovedDueToConflict = Label.createLabel(logLevelLabel, "coruscant-west-2", null);
LabelFactory labelFactory = new LabelFactory(
Mockito.mock(XmlConfiguration.class),
logLevelLabel,
thisShouldBeRemovedDueToConflict,
thisShouldStay
@@ -142,7 +156,7 @@ class LabelFactoryTest {
// Given
String logLevelLabel = "log-level";
LabelFactory labelFactory = new LabelFactory(logLevelLabel);
LabelFactory labelFactory = new LabelFactory(null, logLevelLabel);
// When
LabelsDescriptor actual = labelFactory.convertLabelsDroppingInvalid();
@@ -155,7 +169,7 @@ class LabelFactoryTest {
void shouldHandleDisabledLogLevelLabel() {
// Given
String logLevelLabel = null;
LabelFactory labelFactory = new LabelFactory(logLevelLabel);
LabelFactory labelFactory = new LabelFactory(null, logLevelLabel);
// When
LabelsDescriptor actual = labelFactory.convertLabelsDroppingInvalid();
@@ -167,4 +181,8 @@ class LabelFactoryTest {
public Map.Entry<String, String> asEntry(Label label) {
return Map.entry(label.getName(), label.getValue());
}
public Map.Entry<String, String> asEntryUsingPattern(Label label) {
return Map.entry(label.getName(), label.getPattern());
}
}
@@ -9,9 +9,9 @@ class LabelTest {
@Test
void shouldValidateNamePattern() {
// Given
Label correctLabel = Label.createLabel("aaa_bbb_ccc", "fobar");
Label tooShortLabel = Label.createLabel("", "foobar");
Label labelWithInvalidChars = Label.createLabel("log-level", "foobar");
Label correctLabel = Label.createLabel("aaa_bbb_ccc", "fobar", null);
Label tooShortLabel = Label.createLabel("", "foobar", null);
Label labelWithInvalidChars = Label.createLabel("log-level", "foobar", null);
// When & Then
assertThat(correctLabel.hasValidName()).isTrue();
@@ -55,7 +55,7 @@ class LabelsContextSubstitutionTest {
Logger logger = LogManager.getLogger(LabelsContextSubstitutionTest.class);
// When
MDC.put("object", "bust_ticket");
MDC.put("object", "bus_ticket");
MDC.put("owner", "wally");
logger.info("Test1");
@@ -90,7 +90,7 @@ class LabelsContextSubstitutionTest {
.body("status", equalTo("success"))
.body("data.result.size()", equalTo(3))
.body("data.result.stream.server", everyItem(equalTo("127.0.0.1")))
.body("data.result.stream.object", contains("prefix_", "prefix_bust_ticket", "prefix_comb"))
.body("data.result.stream.object", contains("prefix_", "prefix_bus_ticket", "prefix_comb"))
.body("data.result.stream.owner", contains("_suffix", "wally_suffix", "jennifer_suffix"))
.body("data.result.stream.default_value_test", contains("use_this_if_missing", "use_this_if_missing", "use_this_if_missing"))
.body("data.result.values",
@@ -0,0 +1,124 @@
package pl.tkowalcz.tjahzi.log4j2.labels;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.config.NullConfiguration;
import org.apache.logging.log4j.core.impl.JdkMapAdapterStringMap;
import org.apache.logging.log4j.core.impl.MutableLogEvent;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.apache.logging.log4j.core.pattern.PatternFormatter;
import org.apache.logging.log4j.core.pattern.PatternParser;
import org.apache.logging.log4j.message.SimpleMessage;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
class Log4jAdapterLabelPrinterTest {
@Test
void shouldHandleEmptyListOfFormatters() {
// Given
Log4jAdapterLabelPrinter printer = new Log4jAdapterLabelPrinter(List.of());
MutableLogEvent event = new MutableLogEvent();
StringBuilder builder = new StringBuilder();
// When
printer.append(event, builder::append);
// Then
assertThat(builder).isEmpty();
}
@Test
void shouldInvokeFormatters() {
// Given
PatternParser patternParser = new PatternParser(null, PatternLayout.KEY, null);
List<PatternFormatter> patternFormatters = patternParser.parse("%mdc{tid} %5p %c{1} - %m");
Log4jAdapterLabelPrinter printer = new Log4jAdapterLabelPrinter(patternFormatters);
Map<String, String> mdcMap = Map.of(
"tid", "req-43tnfwenb"
);
MutableLogEvent event = new MutableLogEvent();
event.setContextData(new JdkMapAdapterStringMap(mdcMap));
event.setLoggerName(getClass().getName());
event.setMessage(new SimpleMessage("Test"));
event.setLevel(Level.ERROR);
event.setLoggerFqcn(getClass().getCanonicalName());
StringBuilder builder = new StringBuilder();
// When
printer.append(event, builder::append);
// Then
assertThat(builder.toString()).isEqualTo("req-43tnfwenb ERROR Log4jAdapterLabelPrinterTest - Test");
}
@Test
void shouldSubstituteEnvVariables() {
// Given
System.setProperty("server", "127.0.0.1");
PatternParser patternParser = new PatternParser(
new NullConfiguration(),
PatternLayout.KEY,
null
);
List<PatternFormatter> patternFormatters = patternParser.parse("${sys:server} %5p %c{1} - %m");
Log4jAdapterLabelPrinter printer = new Log4jAdapterLabelPrinter(patternFormatters);
MutableLogEvent event = new MutableLogEvent();
event.setLoggerName(getClass().getName());
event.setMessage(new SimpleMessage("Test"));
event.setLevel(Level.ERROR);
event.setLoggerFqcn(getClass().getCanonicalName());
StringBuilder builder = new StringBuilder();
// When
printer.append(event, builder::append);
// Then
assertThat(builder.toString()).isEqualTo("127.0.0.1 ERROR Log4jAdapterLabelPrinterTest - Test");
}
private static Stream<Arguments> shouldCorrectlyIdentifyStaticPattern() {
return Stream.of(
Arguments.of("Empty", "", true),
Arguments.of("Literal value", "literal value", true),
Arguments.of("Literal value with variable", "Literal & variable: ${server}", false),
Arguments.of("Contains pattern", "With pattern %c{1}", false)
);
}
@ParameterizedTest(name = "{0}")
@MethodSource
void shouldCorrectlyIdentifyStaticPattern(String name, String pattern, boolean isStatic) {
// Given
PatternParser patternParser = new PatternParser(
new NullConfiguration(),
PatternLayout.KEY,
null
);
List<PatternFormatter> patternFormatters = patternParser.parse(pattern);
Log4jAdapterLabelPrinter labelPrinter = Log4jAdapterLabelPrinter.of(patternFormatters);
// When
boolean actual = labelPrinter.isStatic();
// Then
assertThat(actual).isEqualTo(isStatic);
}
}
@@ -0,0 +1,102 @@
package pl.tkowalcz.tjahzi.log4j2.labels;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import io.restassured.parsing.Parser;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.awaitility.Awaitility;
import org.awaitility.Durations;
import org.junit.jupiter.api.Test;
import org.slf4j.MDC;
import org.testcontainers.containers.BindMode;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.net.URI;
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.core.Every.everyItem;
@Testcontainers
class Log4jPatternsInLabelsTest {
@Container
public GenericContainer loki = new GenericContainer("grafana/loki:latest")
.withCommand("-config.file=/etc/loki-config.yaml")
.withClasspathResourceMapping(
"loki-config.yaml",
"/etc/loki-config.yaml",
BindMode.READ_ONLY
)
.waitingFor(
Wait
.forHttp("/ready")
.forPort(3100)
)
.withExposedPorts(3100);
@Test
void shouldSendData() throws Exception {
// Given
System.setProperty("loki.host", loki.getHost());
System.setProperty("loki.port", loki.getFirstMappedPort().toString());
URI uri = getClass()
.getClassLoader()
.getResource("log4j-patterns-in-labels-configuration.xml")
.toURI();
((org.apache.logging.log4j.core.LoggerContext) LogManager.getContext(false))
.setConfigLocation(uri);
Logger logger = LogManager.getLogger(Log4jPatternsInLabelsTest.class);
// When
MDC.put("tid", "req-230rq9ubou");
logger.info("Test1");
MDC.clear();
logger.info("Test2");
// Then
RestAssured.port = loki.getFirstMappedPort();
RestAssured.baseURI = "http://" + loki.getHost();
RestAssured.registerParser("text/plain", Parser.JSON);
Awaitility
.await()
.atMost(Durations.TEN_SECONDS)
.pollInterval(Durations.ONE_SECOND)
.ignoreExceptions()
.untilAsserted(() -> {
given()
.contentType(ContentType.URLENC)
.urlEncodingEnabled(false)
.formParam("query=%7Bserver%3D%22127.0.0.1%22%7D")
.when()
.get("/loki/api/v1/query_range")
.then()
.log()
.all()
.statusCode(200)
.body("status", equalTo("success"))
.body("data.result.size()", equalTo(2))
.body("data.result.stream.server", everyItem(equalTo("127.0.0.1")))
.body("data.result.stream.class_pattern", everyItem(equalTo("p.t.t.l.l.Log4jPatternsInLabelsTest")))
.body("data.result.stream.sequence_number", contains("2", "1"))
.body("data.result.stream.mdc_tid", contains("", "req-230rq9ubou"))
.body("data.result.values",
hasItems(
hasItems(hasItems("Log4jPatternsInLabelsTest - Test2")),
hasItems(hasItems("Log4jPatternsInLabelsTest - Test1"))
)
);
});
}
}
@@ -37,7 +37,7 @@ class LogLevelLabelConfigurationTest {
.withExposedPorts(3100);
@Test
void shouldWorkWIthNoLogLevelConfigured() throws Exception {
void shouldWorkWithNoLogLevelConfigured() throws Exception {
// Given
System.setProperty("loki.host", loki.getHost());
System.setProperty("loki.port", loki.getFirstMappedPort().toString());
@@ -17,6 +17,8 @@
</PatternLayout>
<Header name="server" value="127.0.0.1"/>
<Label name="server" value="127.0.0.1"/>
<Label name="server" value="127.0.0.1"/>
<Label name="object" value="prefix_${ctx:object}"/>
<Label name="owner" value="${ctx:owner}_suffix"/>
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration packages="pl.tkowalcz.tjahzi.log4j2">
<Loggers>
<Root level="INFO">
<AppenderRef ref="Loki"/>
</Root>
</Loggers>
<appenders>
<Loki name="Loki">
<host>${sys:loki.host}</host>
<port>${sys:loki.port}</port>
<ThresholdFilter level="ALL"/>
<PatternLayout>
<Pattern>%c{1} - %m</Pattern>
</PatternLayout>
<Header name="server" value="127.0.0.1"/>
<Label name="server" pattern="127.0.0.1"/>
<Label name="class_pattern" pattern="%C{1.}"/>
<Label name="sequence_number" pattern="%sn"/>
<Label name="mdc_tid" pattern="%mdc{tid}"/>
</Loki>
</appenders>
</configuration>