tabellarium
Used in:
components
- OverviewOverview
- VersionsVersions
- DependentsDependents
- DependenciesDependencies
<dependency>
<groupId>eu.inqudium</groupId>
<artifactId>tabellarium</artifactId>
<version>1.0.0</version>
</dependency><?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.1.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>eu.inqudium</groupId>
<artifactId>tabellarium</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>tabellarium</name>
<description>
Tabellarium is a resilient Logback appender that ships structured log events to
Apache Kafka. Named after the Roman letter-carrier, it never blocks the sender:
per-topic-class circuit breakers stop hammering a broken route, mandatory
overrides pin the strictest producer-side delivery settings for audit-class
topics (acks=all, idempotence), and a fallback appender catches what cannot
be shipped. Delivery is best-effort transport: events are handed off through
bounded in-memory queues, so a crash, overflow, or expired shutdown budget
can lose events - the appender is not, by itself, a durable audit trail.
</description>
<url>https://github.com/Inqudium/tabellarium</url>
<licenses>
<license>
<name>Apache License, Version 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0</url>
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<id>dirkjink</id>
<name>dirkjink</name>
</developer>
</developers>
<scm>
<connection>scm:git:https://github.com/Inqudium/tabellarium.git</connection>
<developerConnection>scm:git:git@github.com:Inqudium/tabellarium.git</developerConnection>
<url>https://github.com/Inqudium/tabellarium</url>
<tag>HEAD</tag>
</scm>
<issueManagement>
<system>GitHub Issues</system>
<url>https://github.com/Inqudium/tabellarium/issues</url>
</issueManagement>
<!--
Releases are published to the GitHub Packages Maven registry:
mvn -Drevision=<version> source:jar deploy
with a server entry for id "github" (username + token carrying
write:packages) in the deployer's settings.xml. Consumers need a
repository entry plus a read:packages token - see the README's
Installation section. Maven Central would additionally require
namespace verification and GPG signing; kept as a possible
future step, not configured here.
-->
<distributionManagement>
<repository>
<id>github</id>
<name>GitHub Packages</name>
<url>https://maven.pkg.github.com/Inqudium/tabellarium</url>
</repository>
</distributionManagement>
<properties>
<!-- CI-Friendly Version -->
<revision>1.0.1-SNAPSHOT</revision>
<!-- Encoding -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- Java Version (Managed by Spring Boot Parent) -->
<java.version>21</java.version>
<!-- Kotlin Version (Override Spring Boot default) -->
<kotlin.version>2.4.10</kotlin.version>
<!-- Third Party Dependencies (not managed by the Boot BOM) -->
<resilience4j.version>2.4.0</resilience4j.version>
<logstash-encoder.version>9.0</logstash-encoder.version>
<testcontainers.version>2.0.5</testcontainers.version>
<!-- Security pin; see the dependencyManagement entry for the rationale. -->
<lz4-java.version>1.11.2</lz4-java.version>
<!--
JUnit tags excluded from the default test run. The external-contract
tag marks characterization tests of third-party behavior (emitter
call-site contracts of consuming services) that exercise no
tabellarium code; their failures would misdirect maintainers of
THIS library. Run them deliberately with -Pexternal-contract.
The integration tag marks the Testcontainers-based broker tests,
which need a Docker daemon; the default loop stays offline and
fast. Run them deliberately with -Pintegration.
One independent property per excluded tag, concatenated below:
each profile blanks only its own part, so the profiles COMPOSE -
"mvn -Pexternal-contract,integration test" runs both stages.
(With a single shared property, the later-declared profile's
value would win and silently drop the other requested stage.)
The static never-tagged sentinel keeps the list non-empty when
both profiles blank their parts: Surefire tolerates empty list
ENTRIES, but JUnit rejects an entirely empty tag list; excluding
a tag no test carries is a no-op.
-->
<surefire.excluded.external-contract>external-contract</surefire.excluded.external-contract>
<surefire.excluded.integration>integration</surefire.excluded.integration>
<surefire.excludedGroups>never-tagged,${surefire.excluded.external-contract},${surefire.excluded.integration}</surefire.excludedGroups>
<!-- Plugin Versions -->
<flatten-maven-plugin.version>1.8.0</flatten-maven-plugin.version>
<ktlint-maven-plugin.version>3.7.1</ktlint-maven-plugin.version>
<dokka-maven-plugin.version>2.2.0</dokka-maven-plugin.version>
<cyclonedx-maven-plugin.version>2.9.3</cyclonedx-maven-plugin.version>
<jacoco-maven-plugin.version>0.8.14</jacoco-maven-plugin.version>
<!--
Default for the late-bound @{argLine} reference in the Surefire
configuration: JaCoCo's prepare-agent goal overwrites this
property with its -javaagent argument. The empty default keeps
Surefire working when JaCoCo is skipped (-Djacoco.skip=true).
-->
<argLine></argLine>
</properties>
<dependencyManagement>
<dependencies>
<!-- Resilience4j is not in the Boot BOM; the BOM keeps every resilience4j
artifact lockstep-versioned. -->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-bom</artifactId>
<version>${resilience4j.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Testcontainers is not managed by the Boot parent; the BOM keeps
the test-only broker-integration modules lockstep-versioned. -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-bom</artifactId>
<version>${testcontainers.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--
Security pin, raised above the version kafka-clients resolves on its own.
lz4-java <= 1.11.0 crashes the JVM when its JNI XXHash methods are called
with an invalid array reference or an invalid off/len range
(CVE-2026-59949 / GHSA-xx22-p4ch-683r, fixed in 1.11.1). Our own exposure
is nil - the advisory explicitly excludes the case where only the array
CONTENTS are attacker-controlled, which is exactly how Kafka's LZ4 codec
uses it - but the library ships this transitively to every consumer, whose
other code shares the classpath and whose scanners would flag it. Fixing it
here removes both.
Relevant because lz4 is this appender's default compression.type for every
topic class (see TopicClass). Caveat: the offline test suite never performs
real LZ4 compression, so this pin is verified to resolve and build, not to
round-trip. Dependabot keeps it current; drop the pin once kafka-clients
resolves 1.11.1 or newer by itself.
-->
<dependency>
<groupId>at.yawk.lz4</groupId>
<artifactId>lz4-java</artifactId>
<version>${lz4-java.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>${logstash-encoder.version}</version>
<optional>true</optional>
</dependency>
<!-- Logback -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
<!-- SLF4J -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<!-- Kafka client (includes MockProducer for tests) -->
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
</dependency>
<!-- Resilience4j: circuit breaker + Kotlin extensions -->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-circuitbreaker</artifactId>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-kotlin</artifactId>
</dependency>
<!--
Optional metrics integration.
micrometer-core enables the bindMeterRegistry() integration on
the appender, including the circuit-breaker metrics: those are
registered by the appender's own binder (MetricsBindings),
which mirrors resilience4j-micrometer's metric names and adds
a per-appender tag - so resilience4j-micrometer itself is no
longer needed.
<optional>true</optional>: consumers that do not want metrics
get a smaller dependency tree, and the appender does not pull
in a transitively-managed Micrometer version that might
conflict with the application's own Spring Boot BOM.
-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
<optional>true</optional>
</dependency>
<!--
Optional Spring integration. Pulled in only by applications
that use KafkaAppenderMetricsBinding, the @Configuration
helper class that binds the appender to a MeterRegistry on
ContextRefreshedEvent. Non-Spring consumers do not pay this
cost; the class is in the jar but never instantiated.
-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<optional>true</optional>
</dependency>
<!-- Kotlin stdlib -->
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
</dependency>
<!--
Used only by the tests to PARSE the encoder's emitted JSON, independent of which Jackson the
encoder uses internally (logstash-logback-encoder 9.0 moved to Jackson 3 / tools.jackson and no
longer exposes com.fasterxml.jackson transitively).
Deliberately test-scoped: the shipped code contains no Jackson reference at all, so putting
jackson-databind on consumers' runtime classpath would enlarge their attack surface (this is
the library with the JVM's longest deserialization-gadget CVE history) and could silently pin
or upgrade the Jackson version of applications that manage it themselves.
-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<scope>test</scope>
</dependency>
<!-- Test scope -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<!-- Test scope: ApplicationContextRunner for the Spring binding tests. -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<scope>test</scope>
</dependency>
<!--
Test scope: real-broker integration tests (tag "integration",
excluded from the default run - see surefire.excludedGroups).
Versions are managed by the Spring Boot BOM.
-->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-kafka</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
<resources>
<!-- Filter ONLY the version resource (MessageEnricher reads it for
the meta.agent.version header); everything else is copied
verbatim so binary resources can never be corrupted by
accidental placeholder substitution. -->
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>tabellarium-version.properties</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
<excludes>
<exclude>tabellarium-version.properties</exclude>
</excludes>
</resource>
</resources>
<plugins>
<!-- Flatten Plugin (resolves the CI-friendly 1.0.0 for install/deploy) -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>flatten-maven-plugin</artifactId>
<version>${flatten-maven-plugin.version}</version>
<configuration>
<updatePomFile>true</updatePomFile>
<flattenMode>resolveCiFriendliesOnly</flattenMode>
<outputDirectory>${project.build.directory}</outputDirectory>
<flattenedPomFilename>.flattened-pom.xml</flattenedPomFilename>
</configuration>
<executions>
<execution>
<id>flatten</id>
<phase>process-resources</phase>
<goals>
<goal>flatten</goal>
</goals>
</execution>
<execution>
<id>flatten.clean</id>
<phase>clean</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Kotlin Compiler. No all-open/spring compiler plugin: the only class
Spring proxies or subclasses is KafkaAppenderMetricsBinding, and that
class (plus its listener method) is explicitly declared open. -->
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<configuration>
<jvmTarget>${java.version}</jvmTarget>
<args>
<arg>-Xjsr305=strict</arg>
</args>
</configuration>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Surefire: also pick up Kotlin test classes -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<includes>
<include>**/*Test.java</include>
<include>**/*Test.kt</include>
</includes>
<excludedGroups>${surefire.excludedGroups}</excludedGroups>
<!-- @{argLine} is resolved LATE (after JaCoCo's prepare-agent
set the property), so the coverage agent and the JVM flags
combine instead of the literal argLine overriding the
agent. -->
<argLine>
@{argLine}
--add-opens java.base/java.lang=ALL-UNNAMED
-XX:+EnableDynamicAgentLoading
</argLine>
</configuration>
</plugin>
<!--
Test coverage, always on: the agent attaches via the argLine
property (merged with the JVM flags in the Surefire config -
see the @{argLine} note there) and the report is written at
verify to target/site/jacoco/ (HTML + XML + CSV). The Docs
workflow publishes the HTML report and derives the coverage
badge from the CSV, so the visible numbers are generated,
never hand-maintained. Skip locally with -Djacoco.skip=true.
-->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco-maven-plugin.version}</version>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>verify</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.github.gantsign.maven</groupId>
<artifactId>ktlint-maven-plugin</artifactId>
<version>${ktlint-maven-plugin.version}</version>
<executions>
<execution>
<id>check</id>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
<configuration>
<sourceRoots>
<sourceRoot>${project.basedir}/src/main/kotlin</sourceRoot>
<sourceRoot>${project.basedir}/src/test/kotlin</sourceRoot>
</sourceRoots>
</configuration>
</plugin>
<!--
CycloneDX SBOM of the RESOLVED dependency graph, generated on demand with
`mvn cyclonedx:makeBom` (deliberately not bound to a lifecycle phase, so
`mvn verify` stays fast). The CI dependency scan consumes target/bom.json.
Why an SBOM rather than scanning pom.xml directly: most versions here are
managed by the Spring Boot BOM and therefore do not appear in pom.xml at
all. Only the resolved graph states what actually ships - which is exactly
what a vulnerability scan has to look at.
Test-scoped dependencies are excluded (the plugin's default): they never
reach a consumer, and an advisory in a test-only library should not gate
the build.
-->
<plugin>
<groupId>org.cyclonedx</groupId>
<artifactId>cyclonedx-maven-plugin</artifactId>
<version>${cyclonedx-maven-plugin.version}</version>
<configuration>
<outputFormat>json</outputFormat>
<schemaVersion>1.6</schemaVersion>
</configuration>
</plugin>
<!--
KDoc API reference, generated on demand with `mvn dokka:dokka`
(not bound to the default lifecycle to keep `mvn verify` fast).
The docs workflow publishes the output under /api/ on the
documentation site.
-->
<plugin>
<groupId>org.jetbrains.dokka</groupId>
<artifactId>dokka-maven-plugin</artifactId>
<version>${dokka-maven-plugin.version}</version>
<configuration>
<moduleName>tabellarium</moduleName>
<outputDir>${project.build.directory}/dokka</outputDir>
<!-- Fail on unresolved [Symbol] references (and other Dokka
warnings): KDoc links are only trustworthy carriers when
a red build guards them. reportUndocumented stays off, so
only genuine reference/markup problems gate. Introduced
per the 2026-08-29 comment audit (pass 0). -->
<failOnWarning>true</failOnWarning>
<!-- Rendered on the module index page of the API reference. -->
<includes>
<include>${project.basedir}/docs/api-module.md</include>
</includes>
<sourceLinks>
<link>
<path>src/main/kotlin</path>
<url>https://github.com/Inqudium/tabellarium/tree/main/src/main/kotlin</url>
<lineSuffix>#L</lineSuffix>
</link>
</sourceLinks>
<pluginsConfiguration>
<!--
logo-icon.svg is the magic asset name: it replaces
Dokka's default header logo AND the favicon (the
generated pages link images/logo-icon.svg as icon).
The file is a copy of docs/assets/icon.svg.
-->
<org.jetbrains.dokka.base.DokkaBase>
<customAssets>
<asset>${project.basedir}/docs/assets/logo-icon.svg</asset>
</customAssets>
</org.jetbrains.dokka.base.DokkaBase>
</pluginsConfiguration>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<!--
Includes the external-contract characterization tests (see the
surefire.excludedGroups property) in the test run:
mvn -Pexternal-contract test
-->
<profile>
<id>external-contract</id>
<properties>
<surefire.excluded.external-contract></surefire.excluded.external-contract>
</properties>
</profile>
<!--
Includes the Testcontainers-based real-broker integration tests
(tag "integration") in the test run. Needs a running Docker
daemon: mvn -Pintegration test
-->
<profile>
<id>integration</id>
<properties>
<surefire.excluded.integration></surefire.excluded.integration>
</properties>
</profile>
<!--
Publishes a release to Maven Central via the Sonatype Central
Portal: mvn -Prelease-central -Drevision=<version> deploy
Requires a server entry with id "central" (portal user token)
in the deployer's settings.xml and a GPG key whose agent can
sign non-interactively. Attaches the sources jar and a
Dokka-generated javadoc jar and signs everything - the
Central Portal validates exactly this set. The
central-publishing extension takes over the deploy phase
inside this profile only; the default deploy (GitHub
Packages via distributionManagement) is unaffected outside
it.
-->
<profile>
<id>release-central</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jetbrains.dokka</groupId>
<artifactId>dokka-maven-plugin</artifactId>
<executions>
<execution>
<id>attach-javadoc</id>
<phase>package</phase>
<goals>
<goal>javadocJar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>3.2.8</version>
<configuration>
<keyname>7AA238446CEBCC84</keyname>
</configuration>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.sonatype.central</groupId>
<artifactId>central-publishing-maven-plugin</artifactId>
<version>0.11.0</version>
<extensions>true</extensions>
<configuration>
<publishingServerId>central</publishingServerId>
<autoPublish>true</autoPublish>
<waitUntil>validated</waitUntil>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>