<?xml version="1.0" encoding="UTF-8"?>

<!--
  Example configuration for eu.inqudium.tabellarium.KafkaAppender.

  Two elements deserve special mention:

    1. <appender-ref ref="..."/> inside <appender> attaches a fallback
       appender for when Kafka delivery fails. Optional but strongly
       recommended in production.

    2. <debug>true</debug> affects only startup diagnostics, not
       per-event behavior. Consider leaving it out.

  Every ${...} placeholder below is resolved BEFORE the appender sees
  the value (Logback variable substitution, <springProperty>, Helm
  templating, or Maven resource filtering) - see the configuration
  guide, section "Placeholder resolution", for the full chain.

  See README.md and kafka-appender-config-guide.md for the full
  reference.
-->

<configuration>

    <!-- =================================================================
         Spring-property bridge (works only in logback-spring.xml, where
         Spring Boot drives the Logback initialization). Values from the
         Spring Environment (application.yml, config server, env-var
         relaxed binding) are NOT visible to Logback's own ${...}
         substitution - a literal ${spring.application.name} would stay
         unresolved. <springProperty> imports them as Logback context
         properties under a local name:
         ================================================================= -->
    <springProperty scope="context" name="appName" source="spring.application.name"/>
    <!-- Optional further bridges, e.g. a custom property with default:
    <springProperty scope="context" name="cmdbId"
                    source="myapp.cmdb-id" defaultValue="MyApplication"/>
    -->

    <!-- =================================================================
         Fallback file appender for when Kafka delivery is unavailable
         (circuit breaker open, broker unreachable, etc.). Records that
         cannot be shipped to Kafka are written here as a last resort.
         ================================================================= -->
    <appender name="KAFKA_FALLBACK_FILE" class="ch.qos.logback.core.FileAppender">
        <file>/var/log/myapp/kafka-fallback.log</file>
        <encoder>
            <pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
        <append>true</append>
        <immediateFlush>true</immediateFlush>
    </appender>

    <!-- =================================================================
         Main Kafka appender. Full reference:
         - README.md (design rationale, resilience model, metrics)
         - kafka-appender-config-guide.md (every element, defaults,
           placeholder resolution, topic classes)
         ================================================================= -->
    <appender name="KAFKA" class="eu.inqudium.tabellarium.KafkaAppender">

        <!-- Standard Logback encoder. LogstashEncoder is recommended for
             JSON output that downstream ingestion tools (Splunk, ELK,
             Loki) can parse directly. -->
        <encoder class="net.logstash.logback.encoder.LogstashEncoder">
            <includeMdcKeyName>TRACE_ID</includeMdcKeyName>
            <includeMdcKeyName>REQUEST_ID</includeMdcKeyName>
            <includeMdcKeyName>PROFILE_ID</includeMdcKeyName>
            <customFields>{
                "kubernetes": {
                    "container_name": "${APPLICATION_NAME}",
                    "host": "${NODE_NAME}",
                    "labels": {
                        "app": "${APPLICATION_NAME}"
                    },
                    "namespace_name": "${POD_NAMESPACE}",
                    "pod_name": "${POD_NAME}"
                }
            }</customFields>
        </encoder>

        <!-- Multi-line "key=value" Kafka producer configuration. Helm /
             Spring placeholder substitution happens before the appender
             parses the text. Lines starting with '#' are treated as
             comments. Whitespace around keys and values is trimmed. -->
        <kafkaProducerProperties>
            bootstrap.servers=${KAFKA_BOOTSTRAP_SERVERS:-kafka.example.com:9092}
            security.protocol=SSL

            ssl.keystore.location=/cert/identity.pkcs12
            ssl.keystore.password=${KAFKA_KEYSTORE_PASSWORD}
            ssl.keystore.type=PKCS12

            ssl.truststore.type=JKS
            ssl.truststore.location=/configs/http-trust.jks
            ssl.truststore.password=${KAFKA_TRUSTSTORE_PASSWORD}

            # The appender forces acks=all for AUDIT topics regardless of
            # what is set here. Setting acks at this level applies to the
            # TECHNICAL / PERFORMANCE classes (which have no compliance
            # mandate). See README "Mandatory override policy".
            # acks=1
        </kafkaProducerProperties>

        <!-- Topic routing configuration. With only <defaultTopic>, every
             event goes to that one topic and is treated as TECHNICAL —
             a single Kafka producer is instantiated.

             Optional <mapping> elements route events by SLF4J marker to
             other topics and assign each mapped topic a topic class
             (AUDIT, FUNCTIONAL, TECHNICAL, PERFORMANCE); each class
             named by a mapping activates its own producer and circuit
             breaker with the class's overrides (e.g. acks=all and
             idempotence enforced for AUDIT). See the configuration
             guide, section "Topic routing". -->
        <topicMapping>
            <defaultTopic>my-application.logs</defaultTopic>
            <!-- Optional: class of the default topic itself (and of any
                 unmapped topic). Default is TECHNICAL; set e.g. AUDIT to
                 apply that class's producer tuning and mandatory
                 overrides to the default stream without a marker mapping.
            <defaultTopicClass>FUNCTIONAL</defaultTopicClass>
            -->
            <!--
            <mapping>
                <marker>SECURITY</marker>
                <topic>audit.security</topic>
                <topicClass>AUDIT</topicClass>
            </mapping>
            -->
        </topicMapping>

        <!-- Metadata attached to every Kafka record as headers
             (meta.component, meta.cmdbId, meta.environment) and used by
             the appender to validate that the deployment is identifiable.

             ${STAGE} resolves from an OS environment variable (set by the
             container/deployment); ${appName} comes from the
             <springProperty> bridge at the top of this file. All three
             values are validated non-blank at start() - but an entirely
             UNRESOLVED placeholder keeps its literal text (e.g.
             "${STAGE}"), passes validation, and shows up as an odd
             header value in the log sink. -->
        <environment>${STAGE}</environment>
        <component>${appName}</component>
        <cmdbId>MyApplication</cmdbId>

        <!-- Optional. When true, the appender emits extra startup info
             (active topic classes, mandatory-override violations,
             fallback configuration, and the generated producer settings
             such as the derived client.id and applied class overrides -
             your own values, credentials included, are never repeated)
             to Logback's status manager. Has NO per-event effect.
             Recommended: leave unset or set to false. -->
        <!-- <debug>true</debug> -->

        <!-- Fallback appender. When the Kafka circuit breaker is open or
             a send fails, the event is delivered through this appender
             instead. Without it, failed events are silently dropped. -->
        <appender-ref ref="KAFKA_FALLBACK_FILE"/>

    </appender>

    <!-- =================================================================
         Root logger configuration. Reference the KafkaAppender DIRECTLY:
         wrapping it in an AsyncAppender is NOT recommended - the appender
         already bounds caller-thread blocking internally (max.block.ms,
         circuit breaker, async fallback dispatcher), and default
         AsyncAppender settings weaken the loss semantics. See README,
         "Should I wrap this in a Logback AsyncAppender?".

         Only for sub-100 ms hard-latency SLAs is a wrapper justified,
         and then with non-default settings:

           <appender name="ASYNC_KAFKA" class="ch.qos.logback.classic.AsyncAppender">
             <appender-ref ref="KAFKA"/>
             <queueSize>2048</queueSize>
             <discardingThreshold>0</discardingThreshold>
             <neverBlock>true</neverBlock>
             <includeCallerData>false</includeCallerData>
           </appender>

         (Be aware that neverBlock=true drops events silently without
         routing them to the fallback.)
         ================================================================= -->
    <root level="INFO">
        <appender-ref ref="KAFKA"/>
    </root>

</configuration>
