############################################################################### ### LogWisp Configuration ### Default location: ~/.config/logwisp/logwisp.toml ### Precedence: CLI flags > Environment > File > Defaults ### ### Commented values are the built-in defaults unless marked "example". ### Uncommenting a default is a no-op. ### ### NOTE: network access control (ACL), http/tcp ingest sources, and ### http_client/tcp_client sinks are not available in this version. TLS is ### available on every network source and sink (the [...tls] blocks below), and ### the [...auth] blocks authenticate peers by certificate (mtls) or password ### (scram; credentials via `lw auth`). Unknown keys anywhere in this file ### or in any plugin config are rejected at startup. ### ### Environment overrides use the LOGWISP_ prefix ### (LOGWISP_QUIET, LOGWISP_LOGGING_LEVEL, ...). Bare names are ignored. ### LOGWISP_CONFIG_FILE and LOGWISP_CONFIG_DIR select the file. Paths such as ### pipelines.0.name cannot be set from the CLI or environment at all. ############################################################################### ############################################################################### ### Global Settings ############################################################################### quiet = false # No log of lw's own; pipeline output still flows status_reporter = true # Periodic status logging (30s, DEBUG level); false without a file auto_reload = false # Watch file; read at startup (signals always reread it) color = "auto" # auto|always|never: level names on console sinks; auto on a terminal, NO_COLOR unset ############################################################################### ### Logging (LogWisp's internal operational logging) ############################################################################### [logging] output = "stderr" # file|stdout|stderr|split|all|none; stdout is the console sink's level = "info" # debug|info|warn|error; warn without a file # format = "txt" # raw|txt|json # sanitization = "" # raw|json|txt|shell (empty = logger default) # [logging.file] # Used when output is "file" or "all" # directory = "./log" # name = "logwisp" # max_size_mb = 100 # max_total_size_mb = 1000 # retention_hours = 168.0 # 7 days ## Validated but NOT applied: the console destination comes from `output` above. # [logging.console] # target = "stdout" # stdout|stderr|split ############################################################################### ### Pipelines ### Each pipeline: plugin_sources -> flow (rate_limit|filters|format) -> plugin_sinks ### Names must be unique. 1+ source and 1+ sink required. ############################################################################### [[pipelines]] name = "default" ###============================================================================ ### Flow (processing between sources and sinks) ###============================================================================ ## policy="pass" short-circuits the size check too: enforcing a size cap needs ## rate > 0 AND policy = "drop". # [pipelines.flow.rate_limit] # rate = 0.0 # Entries/second (0 = limiter disabled) # burst = 0.0 # Burst capacity (defaults to rate) # policy = "pass" # pass|drop # max_entry_size_bytes = 0 # 0 = unlimited ## Filters: sequential include/exclude chain, matched against " " # [[pipelines.flow.filters]] # type = "include" # include|exclude # logic = "or" # or|and # patterns = [".*ERROR.*", ".*WARN.*"] # example; RE2 syntax # [pipelines.flow.format] # type = "raw" # raw|json|txt # flags = 0 # Formatter flags (0 = defaults per type) # timestamp_format = "" # Go time layout (formatter default if empty) # sanitizer_policy = "" # raw|json|txt|shell (defaults per type) ## Flow-level heartbeat (fan-out to all sinks, traverses chain links) # [pipelines.flow.heartbeat] # enabled = false # interval_ms = 1000 # Minimum 100 # include_timestamp = false # include_stats = false # format = "txt" # accepted, no effect: heartbeats use flow.format ###============================================================================ ### TLS (shared shape; applies to every network source and sink) ### ### Listeners (tcp/http sinks, tcp_chain/http_chain sources): ### cert_file + key_file, self_signed, or issuer_cert_file + issuer_key_file ### (exactly one); client_auth + client_ca_file enable mTLS. ### Dialers (tcp_chain/http_chain sinks): ### ca_file + server_name, or pin_sha256, verify the server; cert_file + ### key_file present a client identity for mTLS. ### `lw tls ca` / `lw tls cert` make a CA and certificates. ### ### enabled = false Master switch ### cert_file = "" Local certificate ### key_file = "" Private key for cert_file (set together) ### client_auth = false Listener: require and verify a client certificate ### client_ca_file = "" Listener: CA bundle verifying client certs ### ca_file = "" Dialer: CA bundle verifying the server (empty = system) ### server_name = "" Dialer: SNI / name to verify (empty = configured host) ### self_signed = false Listener: make a self-signed certificate at startup; ### it logs the pin_sha256 dialers set (changes on restart) ### issuer_cert_file = "" Listener: make one at startup signed by this CA ... ### issuer_key_file = "" ... and its key (lw tls ca writes both) ### hosts = [] Listener: names/addresses of a made certificate, beyond ### host, os.Hostname(), localhost, 127.0.0.1, ::1 ### insecure_skip_verify = false Dialer: disable verification (never in prod) ### pin_sha256 = "" Dialer: sha256//BASE64 of the server key, in place of ### ca_file; ';' between several ### min_version = "1.3" "1.2" or "1.3". No max_version, no cipher_suites. ### ### TLS alone is a CA membership check: ANY certificate the CA signed is ### accepted. Add an [...auth] block to decide WHICH of them may connect. ###============================================================================ ###============================================================================ ### AUTH (shared shape; sits beside [...tls] on every network source and sink) ### ### Both methods REQUIRE tls.enabled; dialers forbid tls.insecure_skip_verify. ### On the http sink the block gates stream_path AND status_path. ### Listeners (tcp/http sinks, tcp_chain/http_chain sources): ### mtls authorizes the client certificate; REQUIRES tls.client_auth. ### scram admits the users in credentials_file (`lw auth add-user`). ### Dialers (tcp_chain/http_chain sinks): ### mtls pins the server identity. ### scram logs in as username with the password in password_file. ### Chain sources additionally bind the node label to the identity or username. ### A block naming peers or credentials with type = "none" is refused. ### ### type = "none" none | mtls | scram ### identity = "cn" cn | san_dns | san_uri | san_email. Unset under scram; ### set on a scram listener (needs tls.client_auth), the ### certificate's field must equal the username. ### allow = [] mtls: exact identities. Empty allow AND allow_patterns ### admits any identity the CA vouches for (logged WARN). ### allow_patterns = [] mtls: RE2 patterns; anchor them yourself (^...$) ### node_binding = "" Chain sources only, default "force" with mtls or scram: ### none - trust_node governs, as before ### assert - declared label must equal the identity; ### per-entry node labels still follow trust_node ### force - label AND every entry take the identity ### Overrides trust_node. See doc/security.md. ### credentials_file = "" scram listeners: verifiers; read at (re)load, not watched ### token_lifetime_ms = 0 scram, http sink and http_chain source: 10 s to 24 h, 0 = 15 minutes ### username = "" scram dialers ### password_file = "" scram dialers: one trailing line break is trimmed ### trusted_proxies = [] scram http sink: TLS-terminating reverse proxies ### (addresses or CIDRs); browsers log in through them ###============================================================================ ###============================================================================ ### Sources (1+ required) ###============================================================================ ## Null source (testing) # [[pipelines.plugin_sources]] # id = "null_in" # type = "null" [[pipelines.plugin_sources]] id = "default_source" type = "file" [pipelines.plugin_sources.config] directory = "./" # Directory to monitor (required, not recursive) pattern = "*.log" # Glob pattern (* and ? only) ## Tailing an already-open file polls at a fixed 100ms, regardless of this value. check_interval_ms = 100 # Directory rescan interval (min 10) ## raw = true never parses a line; with format type "raw" the file is relayed byte for byte. raw = false # Keep the whole line as the message from = "end" # "end" or "start" of a newly discovered file ## Console source (stdin; one in the whole configuration, ends lw's pipeline at end of input) # [[pipelines.plugin_sources]] # id = "console_in" # type = "console" # [pipelines.plugin_sources.config] # buffer_size = 1000 ## Random source (testing; special=true exercises sanitizer policies) # [[pipelines.plugin_sources]] # id = "random_in" # type = "random" # [pipelines.plugin_sources.config] # interval_ms = 500 # jitter_ms = 0 # Clamped to interval_ms # format = "txt" # raw|txt|json # length = 20 # special = false ## TCP chain source (stdlib listener; receives NDJSON from upstream tcp_chain sinks) ## Topology: jail [file source -> tcp_chain sink] -> host [tcp_chain source -> aggregate sink] # [[pipelines.plugin_sources]] # id = "chain_in" # type = "tcp_chain" # [pipelines.plugin_sources.config] # host = "0.0.0.0" # IPv4 only; "::" or "::1": IPv6 only # port = 9440 # Required # buffer_size = 1000 # max_connections = 0 # 0 = unlimited # read_timeout_ms = 0 # idle deadline, 0 = none # hello_timeout_ms = 10000 # Protocol preamble deadline # trust_node = true # false: label entries by remote address # [pipelines.plugin_sources.config.tls] # enabled = true # example: mTLS listener # cert_file = "/etc/logwisp/tls/server.crt" # key_file = "/etc/logwisp/tls/server.key" # client_auth = true # client_ca_file = "/etc/logwisp/tls/client-ca.crt" # min_version = "1.3" # [pipelines.plugin_sources.config.auth] # authenticate senders # type = "none" # none | mtls | scram; mtls requires client_auth # identity = "cn" # mtls default; under scram unset, set binds the cert to the user # allow = [] # mtls: exact identities; empty = any the CA issued # allow_patterns = [] # mtls: RE2, anchor them yourself # node_binding = "force" # none | assert | force; overrides trust_node # credentials_file = "" # scram: written by `lw auth add-user` ## HTTP chain source (stdlib listener; receives NDJSON batches from upstream http_chain sinks) # [[pipelines.plugin_sources]] # id = "hchain_in" # type = "http_chain" # [pipelines.plugin_sources.config] # host = "0.0.0.0" # or "::" for IPv6 (IPv6 only) # port = 9441 # Required # ingest_path = "/ingest" # Must start with "/" # buffer_size = 1000 # max_body_bytes = 8388608 # per-request cap (8 MiB) # read_timeout_ms = 30000 # trust_node = true # false: label entries by remote address # [pipelines.plugin_sources.config.tls] # enabled = true # example: mTLS listener # cert_file = "/etc/logwisp/tls/server.crt" # key_file = "/etc/logwisp/tls/server.key" # client_auth = true # client_ca_file = "/etc/logwisp/tls/client-ca.crt" # [pipelines.plugin_sources.config.auth] # authenticate senders # type = "none" # none | mtls | scram; mtls requires client_auth # identity = "cn" # mtls default; under scram unset, set binds the cert to the user # allow = [] # mtls: exact identities; empty = any the CA issued # allow_patterns = [] # mtls: RE2, anchor them yourself # node_binding = "force" # none | assert | force; overrides trust_node # credentials_file = "" # scram: written by `lw auth add-user` # token_lifetime_ms = 0 # scram bearer tokens, 10 s to 24 h; 0 = 15 minutes ###============================================================================ ### Sinks (1+ required, fan-out) ###============================================================================ ## Null sink (testing) # [[pipelines.plugin_sinks]] # id = "null_out" # type = "null" [[pipelines.plugin_sinks]] id = "default_sink" type = "console" [pipelines.plugin_sinks.config] target = "stdout" # stdout|stderr ("split" NOT supported) # buffer_size = 1000 # escape = "auto" # auto|always|never: control characters as , auto on a terminal # color = "auto" # auto|always|never: level names in color (default: top-level color) ## File sink (rotating) # [[pipelines.plugin_sinks]] # id = "file_out" # type = "file" # [pipelines.plugin_sinks.config] # directory = "./logs" # Required # name = "output" # Required # max_size_mb = 100 # max_total_size_mb = 1000 # min_disk_free_mb = 0 # 0 = no floor (only negatives become 100) # retention_hours = 168.0 # buffer_size = 1000 # flush_interval_ms = 100 ## HTTP sink (SSE streaming server + JSON status endpoint) ## Without an auth block both endpoints are UNAUTHENTICATED and the stream sends ## Access-Control-Allow-Origin: *. Bind to a trusted interface. # [[pipelines.plugin_sinks]] # id = "http_out" # type = "http" # [pipelines.plugin_sinks.config] # host = "0.0.0.0" # or "::" for IPv6 (IPv6 only) # port = 8081 # Required # stream_path = "/stream" # Exact; starts with "/", clean, none of { } % ? # # status_path = "/status" # Same rules; differs from stream_path # buffer_size = 1000 # Sink input queue # client_buffer_size = 256 # Per-client send queue # write_timeout_ms = 0 # Per-event deadline, 0 = none # max_connections = 0 # 0 = unlimited # login_page = false # GET /auth/login; scram behind auth.trusted_proxies # viewer_page = false # GET /auth/view, needs login_page; scram + trusted_proxies only # (without auth or under mtls the viewer is always on) # [pipelines.plugin_sinks.config.tls] # enabled = true # example # cert_file = "/etc/logwisp/tls/server.crt" # key_file = "/etc/logwisp/tls/server.key" # client_auth = false # client_ca_file = "" # [pipelines.plugin_sinks.config.auth] # gates BOTH stream_path and status_path # type = "none" # none | mtls | scram; browsers: mtls, or scram behind a proxy # identity = "cn" # mtls default; under scram unset, set binds the cert to the user # allow = [] # mtls: exact identities; empty = any the CA issued # allow_patterns = [] # mtls: RE2, anchor them yourself # credentials_file = "" # scram: log in with `lw auth token` # token_lifetime_ms = 0 # scram bearer tokens, 10 s to 24 h; 0 = 15 minutes # trusted_proxies = [] # scram: proxies ending the browsers' TLS; tls may then be off ## TCP sink (streaming server) # [[pipelines.plugin_sinks]] # id = "tcp_out" # type = "tcp" # [pipelines.plugin_sinks.config] # host = "0.0.0.0" # or "::" for IPv6 (IPv6 only) # port = 9090 # Required # buffer_size = 1000 # client_buffer_size = 256 # write_timeout_ms = 5000 # Missed deadline disconnects the client # keep_alive = true # keep_alive_period_ms = 30000 # max_connections = 0 # [pipelines.plugin_sinks.config.tls] # enabled = true # example # cert_file = "/etc/logwisp/tls/server.crt" # key_file = "/etc/logwisp/tls/server.key" # client_auth = true # client_ca_file = "/etc/logwisp/tls/client-ca.crt" # [pipelines.plugin_sinks.config.auth] # authenticate stream readers # type = "none" # none | mtls | scram; mtls requires client_auth # identity = "cn" # mtls default; under scram unset, set binds the cert to the user # allow = [] # mtls: exact identities; empty = any the CA issued # allow_patterns = [] # mtls: RE2, anchor them yourself # credentials_file = "" # scram: readers use `lw auth stream` ## TCP chain sink (stdlib client; forwards to downstream tcp_chain source) ## Do NOT point a chain sink at a chain source in the SAME pipeline: entries ## loop back in and amplify without bound. # [[pipelines.plugin_sinks]] # id = "chain_out" # type = "tcp_chain" # [pipelines.plugin_sinks.config] # host = "10.0.0.1" # Required; IPv6 bare, e.g. "fd00::1" # port = 9440 # Required # node = "" # origin label, default hostname; preserved across hops # buffer_size = 1000 # dial_timeout_ms = 5000 # write_timeout_ms = 5000 # backoff_min_ms = 500 # backoff_max_ms = 30000 # keep_alive = true # keep_alive_period_ms = 30000 # [pipelines.plugin_sinks.config.tls] # enabled = true # example: mTLS dialer # ca_file = "/etc/logwisp/tls/ca.crt" # server_name = "" # insecure_skip_verify = false # cert_file = "/etc/logwisp/tls/client.crt" # key_file = "/etc/logwisp/tls/client.key" # min_version = "1.3" # [pipelines.plugin_sinks.config.auth] # pin the server (mtls) or log in (scram) # type = "none" # none | mtls | scram; both require tls.enabled # identity = "cn" # mtls: cn | san_dns | san_uri | san_email # allow = [] # mtls: exact identities; empty = any the CA issued # allow_patterns = [] # mtls: RE2, anchor them yourself # username = "" # scram # password_file = "" # scram ## HTTP chain sink (stdlib client; batched NDJSON POST to downstream http_chain source) # [[pipelines.plugin_sinks]] # id = "hchain_out" # type = "http_chain" # [pipelines.plugin_sinks.config] # host = "10.0.0.1" # Required; IPv6 bare, e.g. "fd00::1" # port = 9441 # Required # ingest_path = "/ingest" # node = "" # origin label, default hostname; preserved across hops # buffer_size = 1000 # max_batch_count = 100 # max_batch_bytes = 1048576 # flush_interval_ms = 1000 # request_timeout_ms = 10000 # Covers dial + write + response # backoff_min_ms = 500 # backoff_max_ms = 30000 # [pipelines.plugin_sinks.config.tls] # enabled = true # example: mTLS dialer # ca_file = "/etc/logwisp/tls/ca.crt" # cert_file = "/etc/logwisp/tls/client.crt" # key_file = "/etc/logwisp/tls/client.key" # [pipelines.plugin_sinks.config.auth] # pin the server (mtls) or log in (scram) # type = "none" # none | mtls | scram; both require tls.enabled # identity = "cn" # mtls: cn | san_dns | san_uri | san_email # allow = [] # mtls: exact identities; empty = any the CA issued # allow_patterns = [] # mtls: RE2, anchor them yourself # username = "" # scram # password_file = "" # scram