Skip to main content

Zibanu Django Logging

Optional Zibanu Django package that persists application events, mail-delivery status and per-model audit trails to the database.

Overview

zibanu.django.logging turns runtime events into stored database rows. It provides three complementary mechanisms:

  • Database log handler — DbHandler, a logging.Handler subclass referenced from Django's LOGGING configuration, so records emitted through the standard logging module are written as Log rows.
  • Mail logging — the mail_sent signal (its historical name, send_mail, is kept as a deprecated alias — see API Reference) and its on_mail_sent receiver record each outgoing mail's SMTP result as a Log plus a linked MailLog.
  • Model auditing — AuditEntity rows select which models are audited and on which events; a signal factory wires the audit_model receiver to those models' post_save / pre_save / post_delete signals, writing a Log row (with old/new values) for each change. The set of audited models is managed from the Django admin.

This package exposes no REST endpoints; it is a persistence and signal layer only.

Installation

Install the package from its standalone installer:

pip install ./installers/zibanu-django-logging/

Add it to INSTALLED_APPS in mainApp/components/base.py (the app label is zb_logging):

INSTALLED_APPS = [
    # ... existing apps ...
    "zibanu.django.logging",
]

Run migrations to create the Log, MailLog and AuditEntity tables:

python manage.py migrate

Quick Start

Route the standard logging module into the database by adding the handler to your Django LOGGING setting:

LOGGING = {
    "version": 1,
    "disable_existing_loggers": True,
    "formatters": {
        "simple": {
            "format": "[{levelname}] - {asctime} - {module}: {message}",
            "style": "{",
        },
    },
    "handlers": {
        "zibanu": {
            "level": "DEBUG",
            "class": "zibanu.django.logging.lib.handlers.DbHandler",
            "formatter": "simple",
        },
    },
    "loggers": {
        # Do not route the SQL logger through DbHandler: emit() itself runs an
        # INSERT, so letting django.db.backends propagate to the root handler
        # would feed the handler its own queries.
        "django.db.backends": {
            "handlers": [],
            "propagate": False,
        },
    },
    "root": {
        "handlers": ["zibanu"],
        "level": "DEBUG",
    },
}

Transactional semantics. DbHandler writes synchronously on the caller's database connection, inside the caller's transaction: a record logged within an atomic() block that rolls back is lost with the rollback, and once a transaction is broken the insert fails and the record is dropped (routed to handleError). Records that must survive a rollback need an out-of-band channel (file handler, external service).

Any log call now creates a Log row:

import logging

logging.getLogger(__name__).info("User profile updated")

Features

  • DbHandler logging handler that persists each emitted LogRecord as a Log row; failures are routed to handleError, so logging never breaks the caller.
  • mail_sent signal + on_mail_sent receiver that record SMTP delivery status (Log + MailLog) atomically. Deliberately not resilient (unlike DbHandler and audit_model below): a persistence failure propagates, with its original cause intact, to whoever called mail_sent.send().
  • Per-model auditing driven by AuditEntity configuration, with create / update / delete captured as Log rows including serialized new_values and (on update) old_values.
  • Audit detail serialization is resilient: an unserializable field value degrades the row's detail to an error payload instead of aborting the audited model's save()/delete(), and GIS geometry fields (GEOSGeometry/Point/Polygon/MultiPolygon, via the optional zibanu.django.gis package) are serialized as GeoJSON.
  • Sensitive fields (password, token, secret, api_key, private_key by default) are masked with a fixed placeholder before an audit payload is written, including inside any model nested in the dump — configurable via ZB_LOGGING_REDACTED_FIELDS.
  • Runtime rewiring of audit signals from the Django admin — no restart needed when the audit configuration changes.
  • Startup wiring deferred to a background thread that waits for the app registry, so audit signals attach only once every app is fully loaded.

Usage

Basic Usage — logging to the database

With DbHandler configured (see Quick Start), standard logging calls are persisted. To attach a user and source IP to a record, pass a single dict as the log arguments — DbHandler.emit reads user_id and ip_address from it when record.args is a dict:

import logging

logging.getLogger(__name__).info(
    "Sensitive action performed",
    {"user_id": request.user.id, "ip_address": "203.0.113.7"},
)

Basic Usage — recording mail delivery

Dispatch the mail_sent signal after attempting to send a mail; the on_mail_sent receiver stores a Log and a linked MailLog:

from zibanu.django.logging.lib.signals import mail_sent

mail_sent.send(
    sender=MyMailerClass,
    mail_from="noreply@example.com",
    mail_to=["user@example.com"],       # list; stored joined by ";"
    subject="Welcome",
    smtp_error="",
    smtp_code=0,
)

Advanced Usage — auditing a model

  1. Ensure zibanu.django.logging is in INSTALLED_APPS and migrated.
  2. In the Django admin, open Audit Entities, add an entry, pick the target model (dotted app_label.ModelName) from the live choice list, and check the events to audit (on_create, on_update, on_delete). Keep enabled checked.
  3. Saving the entity calls signals_factory(), which rewires the audit signals immediately — no restart required.

From then on, each configured event writes exactly one Log row — an UPDATE fires both pre_save (on_update) and post_save (on_create, created=False) on the audited model, but only the former populates detail, so the event is recorded once, not twice. That row's detail is a JSON payload containing new_values (and, for updates on an existing row, old_values), with the acting user and source IP resolved from the current request stack.

Audit entities can also be created programmatically:

from zibanu.django.logging.models import AuditEntity

AuditEntity.objects.create(
    model_name="auth.User",
    on_create=True,
    on_update=True,
    on_delete=True,
    enabled=True,
)
# Rewire signals so the new rule takes effect without a restart:
from zibanu.django.logging.lib import signals_factory

signals_factory()

Behavior — disabling an entity. Calling signals_factory() without a model argument processes every AuditEntity row, enabled or not: each one is unconditionally disconnected first, then reconnected only if it is still enabled. So setting an entity's enabled=False does disconnect its previously wired handlers, the next time signals_factory() runs with no model argument — which happens automatically on every admin save/delete of any entity, including the one just disabled. Deleting the entity (signals_factory(model=obj, delete=True)) also disconnects it, immediately and without waiting for a general rewire.

API Reference

Models (zibanu.django.logging.models)

  • Log — extends zibanu.django.db.DatedModel (auto created_at / modified_at). Fields: action, sender, detail (text, default ""), ip_address (nullable), user (FK to the user model, nullable, on_delete=SET_NULL — the log row survives deletion of the referenced user). Indexed on user, action and created_at.
  • MailLog — extends zibanu.django.db.Model. One-to-one log link to Log with on_delete=PROTECT (a Log cannot be deleted while a MailLog references it). Fields: mail_from, mail_to, subject, smtp_code (default 0), smtp_error (nullable).
  • AuditEntity — extends zibanu.django.db.Model. Fields: model_name (unique dotted app_label.ModelName), booleans on_create / on_update / on_delete, and enabled (default True). Ordered by model_name. save() normalizes model_name before persisting: a name that resolves against the app registry (case-insensitively) is rewritten to the model's canonical Model._meta.label, so case variants of the same model collapse to one string and unique=True holds under any database collation; a name that does not resolve (stale or mistyped) is instead lower-cased in place — it still can't be resolved to a canonical label, but folding its case still makes two variants of the same unresolvable name collide under unique=True regardless of the database's collation.

Database engine. All three models persist exclusively through the Django ORM on top of the base package's Model / DatedModel classes — no raw SQL, no engine-specific RunSQL migration operations, no required database extension, and no declared dependency on a concrete driver (psycopg, mysqlclient, oracledb, …). The database alias is resolved through the base package's use_db attribute (defaults to "default", never overridden here); this package declares no DATABASE_ROUTERS of its own, so the host project's multi-database routing is respected as-is.

Retention. This package provides no purge mechanism for Log, MailLog or AuditEntity: no management command, no periodic task, no signal or receiver that deletes rows, and no TTL/archival/retention-by-row-count setting. A row is written once, and the package itself never modifies or deletes it afterwards — the only two ways a row disappears are a manual delete through the Django admin (standard delete/delete_queryset permission, unrestricted by this package — see Admin below) or a direct action by a database administrator, both outside the package's scope. Sizing and retention policy for the Log/MailLog tables are a decision of the host project, not of this package.

Logging handler (zibanu.django.logging.lib.handlers)

  • DbHandler(Handler) — subclasses logging.Handler (not StreamHandler). emit(record) builds a Log from the record (action = level name, sender = module/funcName, detail = formatted message, truncated to ZB_LOGGING_DETAIL_MAX_LENGTH characters with a truncation marker — see Configuration), reads user_id / ip_address from extra= attributes on the record (falling back to record.args when it is a dict), and inserts the row with force_insert=True. Exceptions are sent to handleError instead of being raised.

Signals and receivers (zibanu.django.logging.lib)

  • mail_sent (zibanu.django.logging.lib.signals) — django.dispatch.Signal dispatched when a mail is sent. Expected keyword arguments: mail_from (str), mail_to (list), subject (str), smtp_error (str), smtp_code (int). send_mail is kept as a deprecated alias of the same Signal object (the historical, README-documented name, which collides with django.core.mail.send_mail) — it is expected to be removed in the next release; use mail_sent.
  • on_mail_sent(sender, mail_from, mail_to, subject, smtp_error, smtp_code, **kwargs) (zibanu.django.logging.lib.receivers) — receiver connected to mail_sent; creates a Log and a linked MailLog inside a single transaction. Returns None. Accepted exception to this package's resilience rule: there is no try/except around this receiver — unlike DbHandler.emit (routes failures to handleError) and audit_model (catches broadly and logs at error level), a persistence failure here propagates, with its original cause intact, to whoever called mail_sent.send().
  • audit_model(sender, action=None, **kwargs) (zibanu.django.logging.lib.receivers) — generic audit receiver connected by signals_factory. Resolves the acting user/IP from the request stack and writes exactly one Log row per audited event: new_values is populated only for an actual insert (action == "on_create" and created=True) or an update (action == "on_update") — the post_save call that also fires on every UPDATE with action="on_create" and created=False yields an empty payload and inserts nothing, so an UPDATE is logged once (via the pre_save/on_update wiring), not twice. For updates on an existing row it also captures old_values, built as a diff: only the fields whose value actually changed, plus the primary key, on both sides — comparison runs on the raw dump so masking (below) never hides a real change, and a save that changed no field writes no row at all. Before serialization, sensitive field values — named password, token, secret, api_key or private_key by default, configurable via ZB_LOGGING_REDACTED_FIELDS (see Configuration) — are replaced with a fixed placeholder, including inside any model nested in the dump (e.g. a many-to-many row); only model field names are matched, never the keys of a plain mapping such as a JSONField payload. Detail is JSON-encoded with zibanu.django.lib.ExtendedJSONEncoder, which also serializes GIS geometries (GEOSGeometry/Point/Polygon/MultiPolygon) via their .geojson property — duck-typed so this base-package encoder never imports django.contrib.gis; if a field value is still unserializable, the resulting TypeError from json.dumps is caught and detail degrades to {"error": ...} instead of aborting the audited model's save()/delete().
  • signals_factory(model=None, delete=False) (zibanu.django.logging.lib) — reads the relevant AuditEntity rows and (re)connects audit_model to each model's signals (on_create → post_save, on_update → pre_save, on_delete → post_delete) using stable dispatch_uids (zb_audit_<model_name>_<action>) and weak=False. Handlers are unconditionally disconnected before reconnecting, so the function is idempotent. Raises TypeError if model is given and is not an AuditEntity instance (nor None) — a programming error is propagated rather than silently reinterpreted as "rewire every entity". Deliberately refuses to wire audit signals onto this package's own models (app_label == "zb_logging"), to avoid a Log insert re-firing the very signal that created it. ImportError is logged at warning level (not info: it means the deployment runs completely unaudited) and any other exception at error level; failures never break startup, and are also recorded in the module-level wiring_errors list, keyed by lowercased model_name (or a reserved global key for failures not attributable to a single entity, e.g. an ImportError) — a no-model call clears and repopulates every entry, while a single-entity call (model=<AuditEntity>) clears and repopulates only that entity's entry, leaving every other entity's recorded warning untouched (the global entry is always cleared and retried, regardless of scope) — surfaced as admin warnings by AuditEntityAdmin.changelist_view (see Admin below). See the Behavior note above regarding disabled entities.

Application config (zibanu.django.logging.apps)

  • ZbDjangoLogging(AppConfig) — name = "zibanu.django.logging", label = "zb_logging". ready() starts a background thread (after_ready()) that blocks on the app-registry ready event and then calls signals_factory(), so audit signals are wired only after all apps are loaded — except for a fixed set of management commands (makemigrations, migrate, collectstatic, makemessages, compilemessages, check, showmigrations, sqlmigrate, inspectdb), detected from sys.argv, for which ready() returns immediately without starting the thread: none of them write audited models, and some run before the schema (hence the AuditEntity table) exists. Every other command — runserver, shell, test, createsuperuser, custom commands — still defers normally. after_ready() waits up to 60 seconds on apps.ready_event (falling back to a bounded poll on apps.ready if a future Django release removes that attribute) before calling signals_factory(); an unexpected exception from the call itself is caught and logged at error level, and if the registry is never marked ready within the timeout, a warning is logged instead and signals are never wired for that process. Either way, the thread closes its own (thread-local) database connections in a finally block before exiting.

Admin (zibanu.django.logging.admin, admin_views)

  • AuditEntityAdmin(ModelAdmin) — registered for AuditEntity. Bulk actions are enabled (including Django's default bulk delete, unlike LogAdmin/MailLogAdmin below); list_display = ("model_name", "on_create", "on_update", "on_delete", "enabled"), list_filter = ("enabled",), search_fields = ("model_name",), and fieldsets group the model name, the three audited-event flags and enabled into separate sections. get_form populates the model_name widget with the live model choices (get_model_choices), computed once per process and cached (the app registry cannot change without a restart); a blank first option is prepended (otherwise editing an entity whose stored model_name fell out of the live choices would make the browser preselect another model and silently repoint the rule on save), and a stale stored value is kept as its own choice labeled "(model no longer available)" instead of disappearing. The field is rebuilt as a plain ChoiceField so the submitted value is validated against the live choices, not just the widget's rendered options. save_model calls super().save_model() and then unconditionally signals_factory() (so the rewire acts on the configuration just persisted); when the edit renames model_name, it additionally disconnects the previous model's handlers first — via signals_factory(model=AuditEntity(model_name=previous_name), delete=True), called before super().save_model() while the DB row still holds the pre-edit name — since the general rewire that follows only processes rows currently in the table and would otherwise never unhook the old model. delete_model calls signals_factory(model=obj, delete=True) before deleting; delete_queryset (bulk admin deletes) does the same for every selected entity, mirroring delete_model. changelist_view surfaces any recorded signals_factory.wiring_errors as admin warnings, so a broken audit configuration is visible where it is managed.
  • LogAdmin(ModelAdmin) — read-only admin for Log: has_add_permission/has_change_permission both return False and every field is listed in readonly_fields, since rows are evidence written only by the package's handlers/receivers. actions = None disables bulk actions (including bulk delete); list_display = ("created_at", "action", "sender", "user", "ip_address"), list_filter = ("action", "created_at"). search_fields = ("^sender",) — a prefix match (istartswith) that can use IDX_logging_log_sender; detail is deliberately excluded from search, since an unindexed LIKE over that never-pruned TextField would turn the change-list search box into an unbounded full scan.
  • MailLogAdmin(ModelAdmin) — read-only admin for MailLog, same policy as LogAdmin (has_add_permission/has_change_permission return False, actions = None). list_display = ("log_link", "mail_from", "mail_to", "subject", "smtp_code"), list_filter = ("smtp_code",), search_fields = ("mail_to", "subject"), readonly_fields covers every field. log_link renders the linked Log row as an anchor to its own admin change page.

Configuration

  • LOGGING (Django setting) — reference zibanu.django.logging.lib.handlers.DbHandler as a handler class to persist logging output to the Log table (see Quick Start).
  • ZB_LOGGING_DETAIL_MAX_LENGTH (int, default 10000) — read by DbHandler.emit. Caps the length of the formatted detail written to the Log row; a message longer than this is truncated to the limit and appended with a " … [truncated]" marker, so an arbitrarily large formatted traceback cannot be written verbatim into a table that is never pruned.
  • ZB_LOGGING_REDACTED_FIELDS (iterable of str, default: password, token, secret, api_key, private_key) — read by audit_model. Field names (matched exactly, case-insensitively) whose values are replaced with a fixed placeholder before an audit payload is JSON-encoded into Log.detail, so credentials never persist there. Replaces the default set entirely rather than extending it; a bare string value is treated as a single field name rather than iterated character by character.
  • Audit configuration — managed as AuditEntity rows through the Django admin (or the ORM). No other dedicated settings.py keys are required by this package.

Testing

The package's unit tests live in zibanu/django/logging/tests/ (one test_*.py module per unit under test — the Log/MailLog/AuditEntity models, DbHandler, the mail_sent signal, the audit_model/on_mail_sent receivers, signals_factory, the admin classes and ZbDjangoLogging).

# Run every test of this package
python manage.py test zibanu.django.logging.tests

# Run a specific module, class or method
python manage.py test zibanu.django.logging.tests.test_signals_factory
python manage.py test zibanu.django.logging.tests.test_audit_entity_model.AuditEntitySaveNormalizationTest

# Verbose output
python manage.py test --verbosity=2

Every DB-accessing test (model persistence, DbHandler.emit, the receivers, signals_factory's effect on real rows) runs against the project's own local/dev database — isolation relies on TestCase's per-test transaction rollback, never TransactionTestCase, which would truncate real tables. DB-free units (field/Meta checks, the app-config command-skip gate, the bare signal object) use django.test.SimpleTestCase instead.

Requirements

  • Python 3.11+
  • Django and Django REST Framework
  • The Zibanu base package zibanu.django — this package imports ExtendedJSONEncoder, ModelName, get_model_choices, get_user, get_ip_address and get_request_from_stack from zibanu.django.lib / zibanu.django.lib.utils, and the base model classes from zibanu.django.db.

No dependency on any other optional Zibanu package.

License

Developed by CQ Inversiones SAS. Copyright © 2019–2026. All rights reserved.

Changelog

All notable changes to this package are documented here, one section per principal version (x.y.z). Pre-releases of the same version (-dev.N, etc.) are folded into its section.

[2.0.0] — 2026-10-01

No tag exists yet for this version, and the package is still on a pre-release (2.0.0-dev.5) as of this writing.

Added

  • Read-oriented Django admin registered for Log and MailLog (list/search/filter), plus an audit-entity changelist showing which event hooks (create/update/delete/ save-error) are wired for each entity (LOG-S21, LOG-S25).
  • Sensitive fields (password, token, secret, api_key, private_key by default, configurable via ZB_LOGGING_REDACTED_FIELDS) are now masked before an audited model is serialized into Log.detail, including fields nested inside a related model value — so a credential on an audited model, or on one it embeds, is never persisted in the clear (LOG-R02, ZBDJ-629, ZBDJ-666, ZBDJ-667). A ZB_LOGGING_REDACTED_FIELDS set to a single string is normalized into a one-item list instead of being iterated character by character, which would otherwise mask nothing without any error (ZBDJ-667).
  • AuditEntityAdmin's bulk actions, including Django's default bulk delete, are enabled — delete_queryset already disconnected each selected entity's audit signals before removal, so no extra wiring was needed (GAP-CFG-02).
  • MailLog's admin list links each row to its associated Log entry (ZBDJ-685); Log.sender gained a database index and LogAdmin now searches it by prefix (istartswith) instead of a substring match, so the search box can use the new index instead of an unbounded scan (ZBDJ-651).

Changed

  • Breaking: DbHandler now subclasses logging.Handler instead of logging.StreamHandler — the correct base for a database sink. A consumer that configured this handler with a stream argument, or relied on StreamHandler-specific behavior, needs to drop that configuration (LOG-S53).
  • Breaking: Log.user's foreign key on_delete changed from CASCADE to SET_NULL, so deleting a User no longer deletes their audit trail; requires running the accompanying migration (LOG-I7).
  • Breaking: an update (on_update) now records only the fields whose value actually changed in old_values/new_values, instead of the full object; both dicts share the same key set, the primary key is always included as an identifier (never counted as a change on its own), and no row is written at all when the diff is empty. Masking is applied to the already-computed diff, not before comparing, so two different values of a redacted field are never collapsed into the same placeholder and silently dropped from the diff. on_create/on_delete are unaffected and keep logging the full object (ZBDJ-661, ZBDJ-662, ZBDJ-663).
  • A nested model's own many-to-many fields are excluded from its dump inside an audit payload, so masking a related model no longer issues one extra query per many-to-many field on every related row it holds (ZBDJ-664).
  • The receiver connected to mail_sent was renamed on_send_mail → on_mail_sent, matching the signal it handles instead of the historical send_mail alias; new rows record Log.action="on_mail_sent" (previously "on_send_mail" — existing rows keep the old value). It now also truncates the sender class name to Log.sender's column width, so an unusually long name can no longer raise DataError from inside the signal handler (ZBDJ-628), and normalizes a plain-string mail_to into a single-recipient list instead of silently joining it character by character.
  • MailLog.mail_to and MailLog.smtp_error widened to TextField, so a long recipient list or a real multi-line SMTP rejection message no longer overflows on insert (LOG-I8, LOG-S29).
  • MailLog.smtp_error dropped null=True in favor of a single "no value" representation, default=""; existing NULL rows are backfilled by the accompanying migration (LOG-S14, LOG-R05).
  • Added an index on Log.created_at for retention/cleanup queries, and normalized AuditEntity.model_name storage so it agrees with its case-sensitive lookup; a name that still fails to resolve against the app registry (typo, an app not yet installed) is now lower-cased rather than stored verbatim, so two case-variants of the same unresolvable name collide under unique=True regardless of the database's collation (LOG-S30, LOG-S31, GAP-CFG-03).
  • Internal lib package surface cleaned up: wildcard re-exports replaced with explicit imports and __all__ at each level; the public entry point zibanu.django.logging.lib.signals_factory is unchanged (LOG-I22, LOG-I23, LOG-S34–S37, LOG-S51, LOG-S52).
  • Admin: the model-choices list for audit entities is cached instead of walking the app registry on every render (LOG-S23); editing an entity whose stored model_name is no longer among the live choices now keeps that stale value visible (labeled as no longer available) instead of letting the widget silently substitute another model on save, and renaming model_name on an existing entity now disconnects the previous model's signal handlers before the rename is persisted.
  • audit_model now isolates its own failures (catches and logs at error level, mirroring DbHandler) instead of letting an unexpected exception propagate into the audited model's own save()/delete(); on_mail_sent is deliberately left unwrapped, so a persistence failure there still propagates to the caller (LOG-R05).
  • Signal-wiring failures are now tracked per audited entity rather than as one shared list: deleting or disabling a single entity clears only that entity's recorded failure, instead of blanking the warnings recorded for every other entity.

Fixed

  • Admin: entity save/signal rewiring now runs after super().save_model(), so it reads post-save state instead of stale pre-save state; a crafted admin POST can no longer target an arbitrary model by name; bulk-deleting entities from the admin now unwires each deleted entity's signal handlers (LOG-C1, LOG-I12, LOG-S24).
  • App startup: the deferred signal-wiring thread is now a bounded, daemon thread that closes its own database connection, so it can no longer block interpreter shutdown or leak a connection (LOG-I9, LOG-I11); the gate that skips wiring during management commands (migrate, makemigrations, etc.) now correctly recognizes the subcommand even when it follows a leading option (e.g. manage.py --settings=x migrate).
  • Signal wiring: disabling an audit entity now disconnects all of its rows (previously some stayed connected); a wiring failure on one entity no longer aborts wiring for every entity after it; wiring failures are now recorded in an observable list instead of only being logged (LOG-I13, LOG-I14, LOG-S38).
  • Change detection: object creation is now detected from instance.pk instead of a created flag that pre_save never actually receives, and delete logging is now populated from the in-memory instance post_delete still holds — both previously produced inaccurate or missing audit rows (LOG-I16, LOG-I17).
  • DbHandler.emit: a thread-local guard prevents an INSERT-triggered log record from recursively re-entering emit() (LOG-I20).
  • audit_model no longer raises when a field holds a value the JSON encoder can't serialize (e.g. a GIS geometry) — it now degrades to an {"error": ...} detail instead of propagating the exception into the caller's save()/delete() (LOG-C2).
  • Duplicate/mislabeled audit rows on updates: an UPDATE was writing a second row incorrectly labeled on_create, because post_save fires with created=False on every update, not just inserts. new_values is now populated only for a genuine create or update, and no row is written when the resulting detail is empty.
  • Fixed a name collision where lib/utils/__init__.py's from .signals_factory import signals_factory re-export shadowed the signals_factory submodule with the function of the same name, so code that imported the module itself (to read its wiring_errors list) would have raised AttributeError. The submodule was renamed to signals_factory_module.py; the public zibanu.django.logging.lib.signals_factory function is unchanged.

Documentation

  • Transaction/rollback semantics of DbHandler, and the recommended LOGGING example excluding django.db.backends, documented in the README (LOG-I20, LOG-I21).
  • README (EN/ES) updated to describe the one-row-per-event guarantee and the GIS-geometry serialization fix above.
  • MailLog.smtp_error's empty-string default documented as the field's only "no value" representation (no more None), following the null=True removal above (LOG-S14).
  • README corrected to describe disabling an audit entity accurately: a signals_factory() call with no model argument processes every entity (enabled or not), disconnecting each one first and reconnecting only the still-enabled ones — so disabling an entity does disconnect it, on the next general rewire (which happens on every admin save/delete of any entity), not only when the entity is deleted (ZBDJ-656).
  • README documents that on_mail_sent is deliberately excluded from this package's general exception-resilience rule (ZBDJ-683), and its own docstrings and the mail_sent signal's docstring now reference the current receiver name (ZBDJ-678).
  • README documents DbHandler's transactional semantics (ZBDJ-648) and updates the recommended LOGGING example (README.es.md) to exclude django.db.backends from the routing to DbHandler (ZBDJ-643).
  • README documents the cleanup scope of the wiring-failure inventory and the ImportError-at-info-level detail (ZBDJ-674), the three models' database-engine agnosticism (ZBDJ-636), and the absence of automatic purging — retention is the hosting project's responsibility (ZBDJ-634).
  • README (EN/ES) refreshed for internal consistency after the GAP-CFG-01/ GAP-CFG-02/GAP-CFG-03 changes above and several other prose/behavior divergences found in the same pass.

[1.2.5] — never released (superseded by 2.0.0)

Only ever existed as 1.2.5-dev.0: development jumped straight to 2.0.0-dev.0 without ever releasing a 1.2.5, so no consumer could have installed this version.

Added

  • Log and MailLog gained __str__ representations (LOG-S13).

Changed

  • AuditEntity.model_name uniqueness now enforced with unique=True directly, replacing a single-field unique_together; Log/MailLog saves wrapped in transaction.atomic() (LOG-S12, LOG-S6).
  • Internal cleanup: dropped a redundant .all() on an already-filtered queryset, renamed a loop variable that shadowed the model parameter, replaced zip(get_model_list(), get_model_list()) with get_model_choices(), and removed several unused imports (LOG-S1, LOG-S2, LOG-S9, LOG-S5, LOG-S15, LOG-S17, LOG-S18).

Fixed

  • Signal wiring: dispatch_uid is now stable and connects with weak=False, so a later disconnect actually matches the original connect — previously this could leave stale handlers or silently fail to reconnect (LOG-I1).
  • DbHandler.emit: guards record.args before treating it as a mapping, and now calls self.handleError(record) on failure instead of silently swallowing the exception (LOG-I2, LOG-I3).
  • Mail logging: SMTP errors are now recorded on the MailLog row instead of being reset to None before saving (LOG-I4).
  • Admin: the model-choices list for audit entities is now built per request instead of being frozen at import time, so a newly-registered model appears without restarting the process (LOG-I5).
  • The acting user is now coerced to None before being assigned to Log.user for anonymous requests, instead of assigning an anonymous-user object incompatible with the foreign key (LOG-S7).
  • Mail-sending logs now use the literal action="on_send_mail" instead of inspecting the call stack with inspect.currentframe() (LOG-S8).

Documentation

  • Full NumPy-style docstrings added across models, DbHandler, signal receivers, signals_factory, AppConfig, and admin views; README rewritten and a Spanish translation (README.es.md) added. The signals_factory connect/disconnect asymmetry was documented as real, intentional behavior — no logic changed by this pass.
  • Documented why the deferred signal wiring runs on the AppConfig's ready_event thread rather than in ready() itself (LOG-S16).

[1.2.4] — 2025-03-18

There is no 1.2.4 tag. Note that the repository's only tag, 1.2.3, actually marks a later point (dated 2025-08-27) at which the package version already read 1.2.4 — the tag name is out of sync with the version it marks, so it cannot be used to identify what 1.2.3 contained.

Changed

  • Admin app label changed from "Zibanu Logging" to "Zibanu Django Logging" (apps.py, verbose_name).

Note: the commit that introduced this version is titled after a GIS model, but its actual diff touches only the version file and the one-line admin label change above — no GIS code. This looks like a commit-message artifact carried over when this package's history was extracted from the former Zibanu Django monorepo into its own repository, not an undocumented feature; read as such rather than as evidence of a missing GIS-related change in this package.

[1.2.3] — 2025-03-14

Initial commit of this package as its own repository, extracted from the former Zibanu Django monorepo. No earlier git history is available in this repository to derive changes from — this section marks the starting point of the tracked history, not a diff.

Metadata

Release files for zibanu-django-logging 2.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for zibanu-django-logging 2.0.0
File Size Uploaded
zibanu_django_logging-2.0.0.tar.gz 84.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for zibanu-django-logging 2.0.0
File Interpreter ABI Platform
zibanu_django_logging-2.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 151.2 kB

Release files / zibanu_django_logging-2.0.0.tar.gz

Download URL zibanu_django_logging-2.0.0.tar.gz
Size 84.6 kB
Tags Source
SHA-256 checksum
How to use checksums
47a1988e80ad8a70172fbf472d30bde90038dde80fcaa5cb7c597b54863a8fe5
BLAKE2b-256 checksum
How to use checksums
36d14c337ec02ec49559815e3440aec59ded69037e6b158dab41fe5a55c4b57e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.13

Release files / zibanu_django_logging-2.0.0-py3-none-any.whl

Download URL zibanu_django_logging-2.0.0-py3-none-any.whl
Size 66.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d319093d713978f0a94ac500c98ba626b70b06708b4ba3e069f9bccf82cf796c
BLAKE2b-256 checksum
How to use checksums
bdee26fae92a798ea15d5a3770870bb56d9597193d877d5757c2059654eb8863
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.13

Release history Release notifications | RSS feed

This release

2.0.0 This release

2 release files

1.2.4

2 release files

1.2.3

2 release files

1.2.2

2 release files

1.2.1

2 release files

1.2.0

2 release files

1.1.2

2 release files

1.1.1

2 release files

1.1.0

2 release files

1.0.1

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page