Plain Logback — ntfy-logback¶
A Logback appender plus a zero-code auto-install via a Logback Configurator SPI. Because it
installs through the logging framework itself, alerting is live at startup — before any DI container
exists.
Use this when you use Logback (with or without Spring) and want ERROR logs to alert (and, with
warnTopic, WARN logs too — see level-routing.md) — via XML, or
with no config at all.
Runnable example: examples/logback — a plain SLF4J app wired through
logback.xml, whose CI-run integration tests
cover the base alert path, sync-vs-async delivery, and the clean-shutdown digest flush.
Install¶
- Maven Central: io.github.pimak:ntfy-logback
<dependency>
<groupId>io.github.pimak</groupId>
<artifactId>ntfy-logback</artifactId>
<version>2.1.0</version>
</dependency>
The appender pulls ntfy-core transitively — you only declare this one artifact.
Base configuration¶
Zero-code auto-install¶
Set config via the environment and the appender attaches itself to the root logger at startup (via a
Logback Configurator SPI) — no logback.xml edit required:
export NTFY_URL=https://ntfy.example.com
export NTFY_TOPIC=my-app-alerts
export NTFY_TOKEN=tk_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
Equivalently pass -Dntfy.url=… -Dntfy.topic=… as JVM system properties, or drop a classpath
ntfy.properties with ntfy.url / ntfy.topic keys.
Classpath endpoints require an explicit opt-in. If the endpoint
urlcomes only from a classpathntfy.properties(nontfy.urlsysprop orNTFY_URLenv var), the auto-install is refused with a warn status: any jar on the classpath can ship such a file, so honoring it blindly would let a compromised dependency redirect your error logs. If the file is yours, opt in with-Dntfy.allow-classpath-endpoint=trueorNTFY_ALLOW_CLASSPATH_ENDPOINT=true(the flag itself is intentionally ignored insidentfy.properties), or set the URL via env/sysprop instead.
Built-in async delivery¶
Delivery is asynchronous by default (since 2.0) — no AsyncAppender wrapper needed: each error
alert is handed to the engine's bounded queue and published by a daemon worker, so a slow or
unreachable ntfy server never blocks the logging thread. Overflow drops fold into the storm digest
rather than being lost. See alert-behavior.md for the full semantics.
<appender name="NTFY_ALERT" class="io.github.pimak.ntfy.logback.LogbackAlertAppender">
<url>https://ntfy.example.com</url>
<topic>my-app-alerts</topic>
<asyncQueueCapacity>1024</asyncQueueCapacity>
</appender>
Set <async>false</async> for pre-2.0 synchronous, inline delivery. The AsyncAppender wrapper
below remains available if you prefer Logback's own offloading (pair it with <async>false</async>
so delivery is not queued twice).
MDC context in alert bodies¶
Name the MDC keys you want rendered into each alert body and they appear as key: value lines just
after the Logger: line, in the order listed:
<appender name="NTFY_ALERT" class="io.github.pimak.ntfy.logback.LogbackAlertAppender">
<url>https://ntfy.example.com</url>
<topic>my-app-alerts</topic>
<includeMdcKeys>correlation-id,tenant</includeMdcKeys>
</appender>
This is an explicit allow-list with no wildcard: nothing from the MDC is published unless you name
its key. Values are read from ILoggingEvent.getMDCPropertyMap(), which Logback captures when the
event is constructed, so the context is correct even behind an AsyncAppender. Values are scrubbed
and length-capped before rendering. See mdc-context.md for the guards and
alert-behavior.md for where the block sits in the body.
Explicit logback.xml¶
Or wire it explicitly. The AsyncAppender wrapper is a production-grade alternative to the built-in
async flag — async + never-block + ERROR-only + flush on shutdown:
<appender name="NTFY_ALERT_RAW" class="io.github.pimak.ntfy.logback.LogbackAlertAppender">
<url>https://ntfy.example.com</url>
<topic>my-app-alerts</topic>
<token>tk_xxxxxxxxxxxxxxxxxxxxxxxxxxxx</token>
<async>false</async>
</appender>
<appender name="NTFY_ALERT" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="NTFY_ALERT_RAW"/>
<queueSize>256</queueSize>
<neverBlock>true</neverBlock>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
</appender>
<shutdownHook class="ch.qos.logback.core.hook.DefaultShutdownHook"/>
<root level="INFO">
<appender-ref ref="NTFY_ALERT"/>
</root>
Startup self-test¶
Alerting only speaks when something breaks, so a revoked token or a wrong topic stays invisible until the first real error alert fails to deliver. The opt-in self-test makes one round-trip at boot and reports a clear diagnostic instead:
<appender name="NTFY_ALERT" class="io.github.pimak.ntfy.logback.LogbackAlertAppender">
<url>https://ntfy.example.com</url>
<topic>my-app-alerts</topic>
<startupPing>probe</startupPing>
</appender>
probe is read-only and publishes nothing; publish sends one low-priority test notification
through the production path and is the only mode that proves alerts are genuinely deliverable
(ntfy grants read and write separately). It runs in the background and never delays startup. Add
<startupPingFailFast>true</startupPingFailFast> to turn a failure into a failed
startup in CI or staging. Full details, including the failure diagnostics, in
configuration.md.
startup-ping covers the primary topic only. The optional WARN route has its own independent flag —
ntfy grants permissions per topic, so a healthy ERROR route proves nothing about the WARN one:
When one route fails and another passed, the diagnosis is published as a notification on the passing
route (startup-ping-notify-failures, on by default), so a broken route reaches you rather than only
a status log.
The example, in full¶
The runnable examples/logback app is wired by exactly this file — CI compiles it and its integration tests run against it, so nothing shown here
can drift from what is actually tested.
<?xml version="1.0" encoding="UTF-8"?>
<!--
Example Logback configuration wiring the ntfy alert appender explicitly. Every ntfy setting is
resolved through Logback variable substitution with a default, so the same file works out of the
box (NTFY_URL / NTFY_TOPIC environment variables, or -Dntfy… system properties) AND lets this
module's integration tests point the appender at a loopback ntfy stand-in via context
properties — the tests run exactly this file.
Alternative: with ntfy-logback on the classpath you need no XML at all — setting NTFY_URL and
NTFY_TOPIC auto-installs the appender through a Logback Configurator SPI (see docs/logback.md).
-->
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="NTFY" class="io.github.pimak.ntfy.logback.LogbackAlertAppender">
<url>${NTFY_URL:-https://ntfy.example.com}</url>
<topic>${NTFY_TOPIC:-my-app-alerts}</topic>
<!-- For an auth-protected topic: <token>tk_xxxxxxxxxxxxxxxxxxxxxxxxxxxx</token> -->
<!-- Built-in async delivery (the default since 2.0): each error alert is handed to a bounded
queue drained by a daemon worker, so a slow or unreachable ntfy server never blocks the
logging thread. Set false for pre-2.0 synchronous, inline delivery — the guarantee a
short-lived process may prefer, since the publish completes before the logging call
returns. -->
<async>${NTFY_ASYNC:-true}</async>
<!-- Storm suppression: at most maxAlertsPerWindow individual alerts per suppressionWindow
(milliseconds); the overflow is reported as one aggregated digest — also flushed on a
clean shutdown, so suppressed errors are never lost. These are the library defaults,
spelled out so the example shows where to tune them. -->
<maxAlertsPerWindow>${NTFY_MAX_ALERTS_PER_WINDOW:-3}</maxAlertsPerWindow>
<suppressionWindow>${NTFY_SUPPRESSION_WINDOW:-180000}</suppressionWindow>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="NTFY"/>
</root>
<!-- Stops the LoggerContext (and therefore the ntfy appender) on JVM exit: the appender's stop()
drains any queued async alerts and flushes the pending storm digest while the HTTP client is
still live. -->
<shutdownHook class="ch.qos.logback.core.hook.DefaultShutdownHook"/>
</configuration>
Going further¶
The base config above covers the common case. Everything else is shared across all adapters and lives in the cross-cutting reference pages:
- Configuration reference — every key and how it resolves (sysprop > env >
ntfy.properties), the Logback XML setters, and duration syntax. - Authentication —
tokenvsusername/passwordand the token-wins rule. - Alert behavior — immediate alerts, storm suppression, and digests.
- MDC context — the
include-mdc-keysallow-list, its guards, and why there is no wildcard. - Level routing — the
warn-topicopt-in: which levels alert and where. - Filtering —
excluded-loggers, theNO_ALERTmarker, and self-exclusion. - Troubleshooting — the diagnostics the engine emits via Logback's
StatusManager, and how to surface them. - Compatibility — tested Logback / JDK versions.