v0.34.0 doc sweep and update
This commit is contained in:
1 parent
87f5576ccf
commit
1abffcec6c
41 files changed
+1447
-2287
No files matched your search
+108
-51
@@ -7,27 +7,36 @@ plugins, sessions, TLS, statistics — hangs off that spine.
|
||||
|
||||
```
|
||||
main
|
||||
└── Service
|
||||
├── Pipeline "app"
|
||||
│ ├── Registry instance tracking, single-instance enforcement
|
||||
│ ├── Session Manager per-pipeline connection/session bookkeeping
|
||||
│ ├── Sources[] plugin instances, keyed by id
|
||||
│ ├── Flow
|
||||
│ │ ├── Rate Limiter optional, token bucket
|
||||
│ │ ├── Filter Chain optional, ordered
|
||||
│ │ ├── Formatter raw | txt | json, with sanitizer
|
||||
│ │ └── Heartbeat optional generator
|
||||
│ └── Sinks[] plugin instances, keyed by id
|
||||
├── Pipeline "audit"
|
||||
│ └── ...
|
||||
└── Status Reporter optional, 30s interval
|
||||
├── Service
|
||||
│ ├── Pipeline "app"
|
||||
│ │ ├── Registry creates each plugin from its factory
|
||||
│ │ ├── Session Manager per-pipeline connection/session bookkeeping
|
||||
│ │ ├── Sources[] plugin instances, keyed by id
|
||||
│ │ ├── Flow
|
||||
│ │ │ ├── Rate Limiter optional, token bucket
|
||||
│ │ │ ├── Filter Chain optional, ordered
|
||||
│ │ │ ├── Formatter raw | txt | json, with sanitizer
|
||||
│ │ │ └── Heartbeat optional generator
|
||||
│ │ └── Sinks[] plugin instances, keyed by id
|
||||
│ └── Pipeline "audit"
|
||||
│ └── ...
|
||||
└── Status Reporter cmd/lw, optional, 30 s: Service.GetGlobalStats
|
||||
```
|
||||
|
||||
Package map:
|
||||
|
||||
- `cmd/lw`: entry point, help, logger bootstrap, signal loop, status
|
||||
reporter.
|
||||
- `internal/config`: typed config schema, loading, top-level validation.
|
||||
- `cmd/lw`: entry point: the command line (`cli.go`: lw's flags and the
|
||||
`auth`, `tls`, `preset` and `config` commands), help, completion, logger
|
||||
bootstrap, signal and reload loop, status reporter.
|
||||
- `cmd/lwconf`: `internal/compose` as WebAssembly, and `lwconf.js`, which
|
||||
loads it: see [Configuration Engine](#configuration-engine).
|
||||
- `internal/config`: option schema (tags, catalogue) and `Decode`; loading
|
||||
from file, environment and flags; presets; offline validation of every
|
||||
setting and plugin option.
|
||||
- `internal/compose`: the configuration engine `lw --tui` and `cmd/lwconf`
|
||||
share. It edits pipelines and writes and reads back the command-line,
|
||||
environment and file forms; it reads no file and links no TLS, HTTP or
|
||||
terminal code.
|
||||
- `internal/service`: owns the pipeline set; start, stop, shutdown, global
|
||||
stats.
|
||||
- `internal/pipeline`: pipeline runtime and per-pipeline plugin registry.
|
||||
@@ -41,7 +50,8 @@ Package map:
|
||||
- `internal/chain`: chain wire protocol: hello preamble, entry codec, backoff.
|
||||
- `internal/tlsx`: the single seam between `TLSOptions` and `crypto/tls`.
|
||||
- `internal/authz`: the single seam between `AuthOptions` and the network
|
||||
plugins: `Admit`, `AuthorizeRequest`, `Greet`, `Prepare`.
|
||||
plugins: `Admit`, `AuthorizeRequest`, `Greet`, `Prepare`; `Authorize`
|
||||
refuses under `scram`.
|
||||
- `internal/netacl`: a listener's `acl` block: address rules, PROXY headers
|
||||
and per-client limits; its `Table` is SCRAM throttling's limiter too.
|
||||
- `internal/session`: session manager and per-instance proxy.
|
||||
@@ -53,15 +63,16 @@ Package map:
|
||||
- `internal/logline`: reads a line of any common shape into time, level,
|
||||
tag, message and fields for the live view; holds the one rule for runes a
|
||||
terminal acts on, which the console sink shares.
|
||||
- `internal/version`: version, commit and build time, stamped at build.
|
||||
- `internal/testutil`: fixtures for tests only.
|
||||
|
||||
## Plugin Registration
|
||||
|
||||
Every plugin registers itself in an `init()` function, and
|
||||
`cmd/lw/bootstrap.go` blank-imports each package to trigger those
|
||||
`init()`s. Adding a plugin therefore means writing the package, calling
|
||||
`plugin.RegisterSource` / `plugin.RegisterSink`, adding one blank import, and
|
||||
adding the type's row to the catalogue in `internal/config/schema.go`, which
|
||||
registration requires.
|
||||
`cmd/lw/bootstrap.go` blank-imports each package to trigger it. Adding a
|
||||
plugin means writing the package, calling `plugin.RegisterSource` /
|
||||
`plugin.RegisterSink`, adding one blank import, and adding the type's row to
|
||||
the catalogue in `internal/config/schema.go`, which registration requires.
|
||||
|
||||
The row holds the type's options struct, whose tags declare its defaults and
|
||||
rules, and whether the type is single. The `console` source is single,
|
||||
@@ -77,10 +88,11 @@ instance of a single type fails, naming both pipelines.
|
||||
starts. Publication is non-blocking: a full subscriber channel increments
|
||||
the source's `dropped_entries` counter. The console source alone waits,
|
||||
since stdin can simply be read later.
|
||||
2. **Flow** applies, in order: rate limit → filter chain → formatter. A drop at
|
||||
any stage ends the entry's life and increments `flow.total_dropped`. The
|
||||
verdict, passed or the stage that dropped it (a filter by its index), goes
|
||||
to the pipeline's tap when a viewer set one, as `lw --tui`'s live view does.
|
||||
2. **Flow** applies, in order: rate limit → filter chain → formatter. A rate
|
||||
limit or filter drop ends the entry's life and increments
|
||||
`flow.total_dropped`. The verdict, passed or the stage that dropped it (a
|
||||
filter by its index), goes to the pipeline's tap when a viewer set one, as
|
||||
`lw --tui`'s live view does.
|
||||
3. The formatter output becomes a `core.TransportEvent`, which carries both the
|
||||
formatted `Payload` and the original structured `Entry`.
|
||||
4. **Dispatch** sends the event to every sink's input channel with a
|
||||
@@ -109,8 +121,7 @@ loses nothing. Each stage drops and counts instead of waiting:
|
||||
|
||||
- Source → subscriber: a full channel drops the entry; source
|
||||
`dropped_entries`. The console source waits instead and reads stdin later.
|
||||
- Flow: the rate limiter, a filter rejection or a format error drops it;
|
||||
`flow.total_dropped`.
|
||||
- Flow: the rate limiter or a filter rejection drops it; `flow.total_dropped`.
|
||||
- Pipeline → sink: a full sink input drops it for that sink only; pipeline
|
||||
`total_dropped_by_sink`. A sink declaring `core.CapBackpressure`, the console
|
||||
and null sinks, makes dispatch wait instead, so a filter loses no line to a
|
||||
@@ -124,11 +135,12 @@ loses nothing. Each stage drops and counts instead of waiting:
|
||||
the view shows them.
|
||||
|
||||
The `tcp_chain` sink is the one deliberate exception. It holds a line across
|
||||
reconnects until it is written or the process shuts down, so a downstream
|
||||
outage propagates backwards as a full input buffer and surfaces as
|
||||
`total_dropped_by_sink` on the pipeline rather than as silent data loss inside
|
||||
the sink. The `http_chain` sink retries a batch with backoff, and drops it only
|
||||
on a non-retryable response or on shutdown (`dropped_batches`).
|
||||
reconnects until it is written or the sink stops (shutdown or reload, after
|
||||
`sink.FlushBound`), so a downstream outage propagates backwards as a full
|
||||
input buffer and surfaces as `total_dropped_by_sink` on the pipeline rather
|
||||
than as silent data loss inside the sink. The `http_chain` sink retries a
|
||||
batch with backoff, and drops it only on a non-retryable response or on
|
||||
shutdown (`dropped_batches`).
|
||||
|
||||
## Concurrency Model
|
||||
|
||||
@@ -138,20 +150,23 @@ on a non-retryable response or on shutdown (`dropped_batches`).
|
||||
- Each network sink runs one broadcast/broker goroutine plus, per connection, a
|
||||
writer goroutine (and for TCP, a reader goroutine that exists only to detect
|
||||
disconnects and refresh session activity).
|
||||
- Chain sinks run a single run-loop goroutine that exclusively owns the
|
||||
connection or the pending batch, so no locking is needed around either.
|
||||
- Chain sinks run one run-loop goroutine that owns the pending line or batch;
|
||||
the `tcp_chain` sink adds a reader goroutine per link that closes the link
|
||||
when the source writes anything or closes it.
|
||||
- Statistics are atomics; configuration and registries use RW mutexes;
|
||||
shutdown is context cancellation plus wait groups.
|
||||
|
||||
### Shutdown ordering
|
||||
|
||||
`Pipeline.Stop` is deliberately ordered so in-flight data drains:
|
||||
`Pipeline.Stop` is ordered so in-flight data drains:
|
||||
|
||||
1. Release dispatch waits on backpressure sinks; stop all sources
|
||||
concurrently, each closing its subscriber channels.
|
||||
2. Wait for the run loop, which ends when every subscription channel closes.
|
||||
3. Stop all sinks concurrently; console and file sinks write their queue
|
||||
first. Then cancel the pipeline context.
|
||||
3. Stop all sinks concurrently: console and file sinks write their queue;
|
||||
network sinks deliver theirs within `sink.FlushBound`, their write or
|
||||
request timeout when set below 2 s, else 2 s. Then cancel the pipeline
|
||||
context.
|
||||
|
||||
A finite source ends on its own: the console source at the end of stdin. The
|
||||
run loop then returns and the pipeline's `Finished` channel closes; once every
|
||||
@@ -176,7 +191,7 @@ The network plugins, by role:
|
||||
- `http_chain` sink: chain protocol, batched POST with retry.
|
||||
|
||||
TLS is built in exactly one place, `internal/tlsx`, which exposes
|
||||
`Server(opts)` for listeners and `Client(opts, host)` for dialers. See
|
||||
`Server(opts, host)` for listeners and `Client(opts, host)` for dialers. See
|
||||
[Security](security.md).
|
||||
|
||||
## Sessions
|
||||
@@ -194,18 +209,17 @@ the corresponding SSE client.
|
||||
|
||||
Authorization decisions do not read session metadata — `internal/authz` makes
|
||||
them from the handshake, the SCRAM exchange or a bearer token, at the point of
|
||||
connection or request, and their outcome is *recorded* in the session. That
|
||||
ordering matters: a session exists only for a peer that was already admitted.
|
||||
See the [mTLS](mtls-auth-plan.md) and [SCRAM](scram-auth-plan.md)
|
||||
authentication designs.
|
||||
connection or request, and their outcome is *recorded* in the session, so a
|
||||
session exists only for a peer that was already admitted. See
|
||||
[Security](security.md#the-auth-block) and
|
||||
[Chaining](chaining.md#wire-protocol).
|
||||
|
||||
## Configuration Reload
|
||||
|
||||
Reload (signal or file watch) rebuilds the entire service:
|
||||
|
||||
1. Signals reread the selected file; watches consume a newly published snapshot.
|
||||
The application owns its manager and detached snapshots; no global mutable
|
||||
target is shared with the running service. Validate every candidate again.
|
||||
1. Signals (`SIGHUP`, `SIGUSR1`) reread the file; a watch takes the published
|
||||
snapshot. Every candidate is validated again.
|
||||
2. Build a **new** service from it. If construction fails, the old service keeps
|
||||
running untouched.
|
||||
3. Shut the old service down, start the new one, and restart the status
|
||||
@@ -222,15 +236,58 @@ certificate rotation. Failed candidates release their session workers. Once the
|
||||
old service has stopped, a bind/start failure requires another corrected reload
|
||||
or process restart.
|
||||
|
||||
## Configuration Engine
|
||||
|
||||
`internal/compose` is what `lw --tui` edits with and what the website runs as
|
||||
WebAssembly, so no part of LogWisp is rewritten in JavaScript. `make wasm`
|
||||
writes the files a site drops in
|
||||
([Installation](installation.md#building-from-source)): `lwconf.wasm` is
|
||||
7.1 MB, 1.9 MB at gzip -9, and the toolchain's `wasm_exec.js` uses no eval,
|
||||
so the page needs only `'wasm-unsafe-eval'` in its `script-src`.
|
||||
|
||||
`lwconf.js` is one dependency-free ES module that knows the calls and shapes,
|
||||
never a key:
|
||||
|
||||
- `load({execURL, execSRI, wasmURL, wasmSRI})` fetches both by their
|
||||
integrity and resolves to the engine.
|
||||
- `fields(schema, role, type)` is a node's form rows, a table's a group.
|
||||
- `value`, `at` and `group` read a node's value and sort problems by node.
|
||||
|
||||
The engine's calls take strings (`lwconf.js` writes objects as JSON) and
|
||||
answer `{"value": ...}` or `{"error", "path", "pipeline", "node", "key"}`, as
|
||||
far as the engine places the refusal:
|
||||
|
||||
- `schema()`: what `lw --schema` prints.
|
||||
- `preset(name, values, dirs)`: a preset's pipelines; `values` maps a key to
|
||||
a string, a list key to strings.
|
||||
- `parse(text, form, dirs)`: the pipelines of a form's pasted text.
|
||||
- `validate(pipelines)`: `{pipelines, problems}`, the pipelines settled
|
||||
(defaults, kinds, ids) and the first problem of each part (a value of the
|
||||
wrong kind too) as `{path, pipeline, node, key, text}`.
|
||||
- `emit(pipelines, form)`: the text of `command`, `environment` or `file`.
|
||||
- `edit(pipelines, op)`: `{pipelines, node}`, the node left chosen. `op` is
|
||||
`{op, pipeline, ...}`:
|
||||
- `add_pipeline` and `rename_pipeline` (`name`), `remove_pipeline`
|
||||
- `add` (`role`, `type`), `retype` (`node`, `type`), `remove` (`node`)
|
||||
- `move_filter` (`from`, `to`)
|
||||
- `set` (`node`, `key`, `values`), `unset` (`node`, `key`)
|
||||
- `dirs` answers whether a path a preset names is a directory,
|
||||
`{"/var/log/app.log": false}`; unanswered, a path ending in `/` is one, one
|
||||
with a glob a pattern, and any other is refused at `path`.
|
||||
- Pipelines are a JSON list in the file's shape (`name`, `flow`,
|
||||
`plugin_sources`, `plugin_sinks`); a misspelled key is refused. A node is
|
||||
`{role, id, index}`: a source's or sink's id, a filter's index.
|
||||
|
||||
## Resource Management
|
||||
|
||||
- Every buffer is bounded; the drop-not-block policy keeps memory flat under
|
||||
load.
|
||||
- The `tcp` and `http` sinks and the `tcp_chain` source accept a
|
||||
`max_connections` cap. Admission is a load-then-check, so a burst can
|
||||
over-admit by roughly one connection.
|
||||
- `max_connections` caps connections on the `tcp` sink and the `tcp_chain`
|
||||
source, and streams on the `http` sink. Admission is a load-then-check, so a
|
||||
burst can over-admit by roughly one.
|
||||
- Chain listeners bound a single line at `core.MaxLogEntryBytes` (1 MiB); an
|
||||
oversized line is a protocol violation and terminates the connection.
|
||||
oversized line terminates a `tcp_chain` link and fails an `http_chain`
|
||||
batch with `400`.
|
||||
- The `http_chain` source caps each request body at `max_body_bytes`.
|
||||
- File sinks rotate on size, cap total rotated size, and honour a retention
|
||||
window.
|
||||
|
||||
Reference in new issue
Block a user