Advertisement
Open Source Projects by Phil Schwartz

Python Log Aggregation with Streaming Database Storage

Building a Python-Based Log Aggregator That Streams Entries to a Central Database requires more than reading files and inserting rows. A useful system must cope with changing log formats, disconnected hosts, bursts of activity, duplicate records and the practical limits of a database connection.

The design suits Linux administrators, developers and small operations teams that want a focused alternative to a large observability platform. Python provides mature libraries for file handling, networking, parsing, scheduling and database access, while a simple architecture keeps the deployment understandable and easy to maintain.

Australian environments add a few considerations. A business may run workloads across Sydney and Melbourne, serve customers from Perth or Brisbane, and operate on a mixture of cloud services, office networks and regional links. Data retention, privacy obligations and reliable operation over variable NBN connections should influence the design from the start.

Define the event pipeline

A log aggregator is easier to reason about when it is divided into stages: collection, transport, parsing, normalisation, storage and querying. The collector watches a local file, systemd journal or application stream. It then sends raw entries to a central service, which adds metadata such as hostname, source path, receipt time and parser version.

The raw message should be preserved alongside structured fields. A parser may identify an IP address, username, HTTP status, SSH action or exception type, but a future investigation may depend on text that the first parser did not understand. Storing both forms makes it possible to improve extraction rules without losing the original evidence.

Each event should have a stable identifier. A hash of the source host, file name, byte offset and message can help detect duplicates, although offsets may change after log rotation. A generated event ID combined with a unique constraint gives the database a second line of defence when a client retries a failed upload.

Collect entries without losing them

For ordinary files, Python can track an inode, current byte position and file size. When the inode changes or the file becomes smaller, the collector should assume rotation has occurred and reopen the replacement. It should also handle copy-truncate rotation, where the existing file is emptied rather than renamed.

A small local queue is valuable. The collector can append events to a durable SQLite spool or newline-delimited queue before attempting delivery. If the central database is offline, the agent keeps reading until the spool reaches a defined limit. At that point, it should apply a documented policy, such as dropping the oldest low-priority messages while retaining security events.

Australian offices can have uneven connectivity, particularly when a site relies on a busy wireless service or a regional NBN connection. Batching several records into one request reduces overhead, while exponential backoff prevents every disconnected agent from reconnecting at the same moment. A practical “have a crack later” retry policy is useful, provided its limits are explicit.

Stream data through a reliable service

The transport layer can use HTTPS with JSON, a message broker, or a lightweight TCP protocol. HTTPS is often the simplest starting point because firewalls and cloud services commonly support it. The sender should include a batch ID, compression where appropriate, and an acknowledgement that confirms the records have been accepted rather than merely received by a web server.

A central ingestion service can be built with Python’s FastAPI, Flask or an asynchronous framework. It should validate payload sizes, authenticate each agent, reject malformed timestamps and return clear status codes. A queue between ingestion and database workers protects the API from short database slowdowns. For higher volumes, Redis streams, RabbitMQ or Kafka can provide durable buffering and consumer coordination.

Authentication deserves careful treatment. Per-host credentials are easier to revoke than one shared secret, while mutual TLS provides stronger identity when the environment justifies the operational effort. Secrets should live outside source code, and an agent should have permission to submit logs without being able to read unrelated records.

Choose a schema that supports investigation

A relational database works well for moderate volumes and operational reporting. A useful event table might contain an internal ID, event time, received time, host ID, application name, severity, facility, source address, message text, structured JSON and a deduplication key. Separate host and parser tables prevent repeated metadata from bloating every record.

Indexes should reflect real searches. Operations staff may filter by host and time range, while security reviews may search by source address or event category. Time-based partitioning makes retention jobs faster and allows old partitions to be removed efficiently. PostgreSQL offers strong indexing and JSON support; SQLite is excellent for an agent spool but is usually a poor choice for a busy central collector.

Time zones can cause quiet errors. Store timestamps in UTC, retain the original offset when it is meaningful, and display them in the operator’s local zone. A team spanning Perth, Adelaide, Sydney and Melbourne needs clear handling of Australian time zones, including daylight saving changes that do not apply uniformly across the country.

Make the tool observable and maintainable

The aggregator needs its own telemetry. Track records read, records accepted, parse failures, queue depth, retry count, oldest unsent event and database latency. A heartbeat from every agent can reveal that a host has stopped reporting even when no application log has been generated.

Parsing should be modular rather than a long chain of regular expressions. Define a parser interface that accepts a raw line and returns a normalised event or a controlled failure. Tests should cover malformed input, multiline exceptions, unusual Unicode, rotated files and timestamps around daylight saving transitions. A compact developer utility such as Mr Sparkle can sit alongside this kind of focused tooling, where a narrowly defined job is easier to test and reuse.

Security controls should cover the entire path. Encrypt traffic in transit, restrict database roles, redact passwords and tokens before storage, and set retention periods that match business needs. Australian organisations may also need to consider the Privacy Act, sector-specific rules and customer expectations about where operational data is held. Keeping sensitive logs in an Australian cloud region can simplify governance, though it does not remove the need for access controls and audit records.

A first release can remain deliberately modest: one file collector, HTTPS batching, a durable local spool, PostgreSQL storage and a small query interface. Once those pieces are reliable, the project can add journal support, richer parsers, alerting and horizontal workers. The result is a practical Python log pipeline that keeps events moving, exposes failures clearly and remains understandable when someone has to troubleshoot it during a busy arvo.