Class NtfyLog4j2Appender

java.lang.Object
org.apache.logging.log4j.core.AbstractLifeCycle
org.apache.logging.log4j.core.filter.AbstractFilterable
org.apache.logging.log4j.core.appender.AbstractAppender
io.github.pimak.ntfy.log4j2.NtfyLog4j2Appender
All Implemented Interfaces:
org.apache.logging.log4j.core.Appender, org.apache.logging.log4j.core.filter.Filterable, org.apache.logging.log4j.core.impl.LocationAware, org.apache.logging.log4j.core.LifeCycle, org.apache.logging.log4j.core.LifeCycle2

@Plugin(name="Ntfy", category="Core", elementType="appender") public final class NtfyLog4j2Appender extends org.apache.logging.log4j.core.appender.AbstractAppender
A thin log4j2 AbstractAppender shell over the framework-neutral AlertEngine, exposed to log4j2.xml as the <Ntfy .../> element. Configuration arrives either through the plugin NtfyLog4j2Appender.Builder (declarative XML/JSON/YAML config) or as a pre-built NtfyConfig injected by NtfyLog4j2Installer for the programmatic one-liner install.

Every decision that matters — logger exclusion (including this library's own package), the NO_ALERT marker gate, storm rate-limiting, digesting, payload assembly, and HTTP publishing — lives in the engine; this class only maps a log4j2 LogEvent onto an AlertEvent and calls AlertEngine.submit(io.github.pimak.ntfy.core.AlertEvent).

Self-diagnoses exclusively through log4j2's StatusLogger (via Log4j2Diagnostics), so no feedback loop through the application's own logging pipeline is possible and no credential ever appears in diagnostic output.

Minimal log4j2.xml:


 <Configuration>
   <Appenders>
     <Ntfy name="ntfy" url="https://ntfy.sh" topic="my-alerts"/>
   </Appenders>
   <Loggers>
     <Root level="info">
       <AppenderRef ref="ntfy"/>
     </Root>
   </Loggers>
 </Configuration>
 
  • Nested Class Summary

    Nested Classes
    Modifier and Type
    Class
    Description
    static class 
    Plugin builder for the <Ntfy .../> element.

    Nested classes/interfaces inherited from interface org.apache.logging.log4j.core.LifeCycle

    org.apache.logging.log4j.core.LifeCycle.State
  • Field Summary

    Fields
    Modifier and Type
    Field
    Description
    static final String
    Marker name a caller attaches to a single log call to opt it out of alerting entirely (logger.error(MarkerManager.getMarker(NtfyLog4j2Appender.NO_ALERT_MARKER_NAME), ...)).

    Fields inherited from class org.apache.logging.log4j.core.AbstractLifeCycle

    DEFAULT_STOP_TIMEOUT, DEFAULT_STOP_TIMEUNIT, LOGGER

    Fields inherited from interface org.apache.logging.log4j.core.Appender

    ELEMENT_TYPE, EMPTY_ARRAY
  • Method Summary

    Modifier and Type
    Method
    Description
    void
    append(org.apache.logging.log4j.core.LogEvent event)
    Gates on level, maps the event eagerly, and hands it to the engine, which owns all further gating and publishing.
    org.apache.logging.log4j.Level
    The log4j2 level this appender should be ATTACHED at — WARN once the engine activated a warn route, ERROR otherwise.
    Read-only pipeline observability counters (published / suppressed / failed) for this appender.
    The plugin builder log4j2 instantiates for an <Ntfy .../> element.
    void
    Builds an AlertEngine from the injected config (if present) or from the plugin attributes, starts it, and marks this appender started ONLY if the engine actually activated.
    boolean
    stop(long timeout, TimeUnit timeUnit)
    Tears the engine down — which flushes any pending digest and drains the async queue — and then delegates to super.stop for the normal appender/filter shutdown.

    Methods inherited from class org.apache.logging.log4j.core.appender.AbstractAppender

    error, error, error, getHandler, getLayout, getName, ignoreExceptions, parseInt, requiresLocation, setHandler, toSerializable, toString

    Methods inherited from class org.apache.logging.log4j.core.filter.AbstractFilterable

    addFilter, getFilter, getPropertyArray, hasFilter, isFiltered, removeFilter, stop

    Methods inherited from class org.apache.logging.log4j.core.AbstractLifeCycle

    equalsImpl, getState, getStatusLogger, hashCodeImpl, initialize, isInitialized, isStarted, isStarting, isStopped, isStopping, setStarted, setStarting, setState, setStopped, setStopping, stop, stop

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait

    Methods inherited from interface org.apache.logging.log4j.core.LifeCycle

    getState, initialize, isStarted, isStopped, stop
  • Field Details

    • NO_ALERT_MARKER_NAME

      public static final String NO_ALERT_MARKER_NAME
      Marker name a caller attaches to a single log call to opt it out of alerting entirely (logger.error(MarkerManager.getMarker(NtfyLog4j2Appender.NO_ALERT_MARKER_NAME), ...)). Re-exposed from AlertEngine.NO_ALERT_MARKER_NAME so callers keep a stable handle instead of a magic string.
      See Also:
  • Method Details

    • newBuilder

      @PluginBuilderFactory public static NtfyLog4j2Appender.Builder newBuilder()
      The plugin builder log4j2 instantiates for an <Ntfy .../> element.
      Returns:
      a fresh builder
    • start

      public void start()
      Builds an AlertEngine from the injected config (if present) or from the plugin attributes, starts it, and marks this appender started ONLY if the engine actually activated. A silently/partially unconfigured engine leaves the appender isStarted() == false (the engine already reported why on the StatusLogger), which is what keeps a misconfigured <Ntfy> element from pretending to alert.
      Specified by:
      start in interface org.apache.logging.log4j.core.LifeCycle
      Overrides:
      start in class org.apache.logging.log4j.core.filter.AbstractFilterable
    • append

      public void append(org.apache.logging.log4j.core.LogEvent event)
      Gates on level, maps the event eagerly, and hands it to the engine, which owns all further gating and publishing. The gate lives here (mirroring the Logback appender's gate and the Quarkus adapter's SEVERE gate) so the documented level contract holds even when the element is referenced from a <Root level="info"> — without it, a root reference would push every INFO/DEBUG line verbatim to the ntfy topic.

      ERROR/FATAL always alert. WARN alerts only when the engine activated a warn route; below WARN nothing ever does, on any configuration. This is the inner of two floors: attachLevel() keeps log4j2 from routing sub-threshold events here in the first place.

      Parameters:
      event - the event log4j2 is dispatching to this appender
    • stop

      public boolean stop(long timeout, TimeUnit timeUnit)
      Tears the engine down — which flushes any pending digest and drains the async queue — and then delegates to super.stop for the normal appender/filter shutdown. Null-safe when never started and safe to call twice; AlertEngine.stop() is itself idempotent. log4j2 routes the no-argument stop() here too, so this is the single teardown path.
      Specified by:
      stop in interface org.apache.logging.log4j.core.LifeCycle2
      Overrides:
      stop in class org.apache.logging.log4j.core.filter.AbstractFilterable
      Parameters:
      timeout - the shutdown timeout log4j2 allows
      timeUnit - the unit of timeout
      Returns:
      whether the appender stopped cleanly
    • attachLevel

      public org.apache.logging.log4j.Level attachLevel()
      The log4j2 level this appender should be ATTACHED at — WARN once the engine activated a warn route, ERROR otherwise. Read by NtfyLog4j2Installer and NtfyLog4j2ReattachListener so the attach-time floor and append(org.apache.logging.log4j.core.LogEvent)'s own floor derive from one value and can never drift apart. Valid only after start(); before it (and after stop()) it reports the safe ERROR.

      This second floor is not redundant: an appender attached at ERROR never has WARN events routed to it at all, so without lowering it the in-appender gate would never see a warning to let through.

      Public because attaching this appender is not something only this package does: the Spring Boot starter's Log4j2 backend builds and attaches one too, and must derive its level from the same value or the two installation paths would disagree.

      Returns:
      the level this appender should be attached to a LoggerConfig at
    • getCounters

      public PipelineCounters getCounters()
      Read-only pipeline observability counters (published / suppressed / failed) for this appender. The returned holder is stable for the appender's whole lifetime — the same instance before and after start()/stop() cycles and across a log4j2 reconfiguration — so the tallies are monotonic and never reset. Counters are pulled here, never logged, preserving the loop-safe design.
      Returns:
      this appender's counters, never null