Skip to main content

OWASP-compliant structured security event logger for Python applications.

Project description

OWASP Logger

PyPi Release GitHub Release Publish to PyPi Commits Since Release

OWASP Logger is a Python library that provides structured, OWASP-compliant security logging.

It enables consistent and machine-readable logs for authentication, authorization, session management, and sensitive data access - built directly on top of the standard logging module.

📦 Installation

You can install the library with uv or pip:

# Standalone logger (without OpenTelemetry dependencies)
uv pip install owasp-logger
# With OpenTelemtry integration
uv pip install owasp-logger[otel]

🚀 Usage

Quick Start

from owasp_logger import OWASPLogger

# Initialize
owasp_log = OWASPLogger(appid="coconut.app")

# Log a security event
owasp_log.authn_login_fail(userid="banana-bob", description="Invalid password attempt")

Output (JSON structured log):

{
  "datetime": "2025-09-26T12:37:37.886421+02:00",
  "appid": "coconut.app",
  "event": "authn_login_fail:banana-bob",
  "level": "WARNING",
  "description": "Invalid password attempt"
}

Normal logging methods (e.g., .info(), .warning(), etc.) are all available and won't follow the OWASP format. All logs emitted via the other functions are OWASP-structured.

See the examples/ folder for more demos:

OpenTelemetry Integration

Installing the package with the extra OpenTelemetry dependency (i.e., owasp-logger[otel]) allows you to emit OWASP-compliant logs in the OpenTelemetry format.

# Assuming you already instrumented your app with an OpenTelemetry logger
...

logger = getLogger("otel")  # OpenTelemetry logger

owasp_log = OWASPLogger(appid="secure-app", logger=logger)
owasp_log.authz_admin(
    userid="ananas-alex",
    admin_activity="watered_plants",
    description="Admin ananas-alex watered their plants",
))

Output (JSON structured log, in the OpenTelemetry format):

{
    "body": "Admin ananas-alex watered their plants",
    "severity_number": 13,
    "severity_text": "WARN",
    "attributes": {
        "owasp_event": {
            "datetime": "2025-10-12T18:24:33.887582+02:00",
            "appid": "example.appid",
            "event": "authz_admin:ananas-alex,watered_plants",
            "level": "WARNING",
            "description": "Admin ananas-alex watered their plants"
        },
        "code.file.path": "/home/aegis/Repositories/Canonical/owasp-logger/src/owasp_logger/logger.py",
        "code.function.name": "_log_event",
        "code.line.number": 35
    },
    "dropped_attributes": 0,
    "timestamp": "2025-10-12T16:24:33.887697Z",
    "observed_timestamp": "2025-10-12T16:24:33.887714Z",
    "trace_id": "0x00000000000000000000000000000000",
    "span_id": "0x0000000000000000",
    "trace_flags": 0,
    "resource": {
        "attributes": {
            "telemetry.sdk.language": "python",
            "telemetry.sdk.name": "opentelemetry",
            "telemetry.sdk.version": "1.33.1",
            "service.name": "example-service"
        },
        "schema_url": ""
    }
}

To learn how to instrument your Python application with OpenTelemetry, check out the official documentation:


🧾 Log Model

Each OWASP log event conforms to the structure presented in the OWASP Logging Vocabulary Cheat Sheet:

{
    # Required fields
    "datetime": "2021-01-01T01:01:01-0700",
    "appid": "foobar.netportal_auth",
    "event": "AUTHN_login_success:joebob1",
    "level": "INFO",
    # Optional fields
    "description": "User joebob1 login successfully",
    "useragent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36",
    "source_ip": "165.225.50.94",
    "host_ip": "10.12.7.9",
    "hostname": "portalauth.foobar.com",
    "protocol": "https",
    "port": "440",
    "request_uri": "/api/v2/auth/",
    "request_method": "POST",
    "region": "AWS-US-WEST-2",
    "geo": "USA"
}

This model is accurately reflected in the model.py file.

When an OWASP log is emitted as part of a structured log which is either OpenTelemetry format or an arbitrary JSON, its information will be nested under the owasp_event key.


🧭 Getting OWASP logs into Grafana Loki

OWASPLogger's logs can be sent to a Loki instance through OpenTelemetry Collector, which acts as a normalization and enrichment layer. The collector itself is available both as a charm and snap.

flowchart LR
    openstack[Openstack]
    sunbeam[Sunbeam]
    landscape[Landscape]
    landscapelog[["*Landscape log files*"]]
    cloud-init[["*cloud-init.log*"]]
    app["Any application"]
    otlplog[["*otlp.log*"]]
    something[Python application using<br>OTel SDK + OWASPLogger]
    cos-proxy["COS Proxy<br>**Charm**"]
    otelcol-snap[OpenTelemetry Collector **Snap**]
    otelcol-charm[OpenTelemetry Collector **Charm**]
    loki2[Loki 2]
    loki3[Loki 3]

    openstack -->|Logs are scraped by| cos-proxy
    sunbeam -->|*filelog* receiver| otelcol-charm
    app -->|Saves OTLP logs to file| otlplog
    otlplog -->|*filelog* receiver| otelcol-snap
    landscape -->|Saves logs to file| landscapelog
    landscapelog -->|*filelog* receiver| otelcol-snap
    cloud-init -->|*filelog* receiver| otelcol-snap
    something -->|Direct write in<br>OTLP format| loki3

    cos-proxy --> otelcol-charm
    otelcol-snap -->|OTLP format| loki3
    otelcol-charm -->|OTLP Format| loki3
    otelcol-charm -->|Loki Push API| loki2

The preferred backend is Loki 3, which preserves structured attributes for consistent search and dashboards.

Why an OpenTelemetry Collector?

Using an OpenTelemetry Collector ensures all logs share a consistent structure and labels, simplifying queries and dashboards. As mentioned in the log model, the OWASP information must be a nested attribute under the owasp_event key:

{
    "body": "Administrator banana-bob has updated privileges of user coconut-charlie from user to admin",
    "severity_number": 13,
    "severity_text": "WARN",
    "attributes": {
        "owasp_event": {
            "datetime": "2025-09-19T14:31:58.140099+02:00",
            "type": "security",
            "appid": "example.appid",
            "event": "authz_admin:coconut-charlie,user_privilege_change",
            "level": "WARNING",
            "description": "Administrator banana-bob has updated privileges of user coconut-charlie from user to admin"
        },
        "code.file.path": "/home/aegis/Repositories/Canonical/owasp-logger/src/owasp_logger/logger.py",
        "code.function.name": "_log_event",
        "code.line.number": 26
    },
    "dropped_attributes": 0,
    "timestamp": "2025-09-19T12:31:58.140189Z",
    "observed_timestamp": "2025-09-19T12:31:58.140207Z",
    "trace_id": "0x00000000000000000000000000000000",
    "span_id": "0x0000000000000000",
    "trace_flags": 0,
    "resource": {
        "attributes": {
            "telemetry.sdk.language": "python",
            "telemetry.sdk.name": "opentelemetry",
            "telemetry.sdk.version": "1.33.1",
            "service.name": "example-service"
        },
        "schema_url": ""
    }
}

The attributes will be mapped as-is to structured metadata in Loki.

Given that logs can have various formats depending on the application, the easiest way to get those attributes in place is to configure some custom processors to parse the OWASP information from your log line. The following sections explain how to do so.

How to configure OpenTelemetry Collector

Logs can arrive in different formts - this section explains how to parse them into OpenTelemetry attributes.

This README classifies logs according to the OpenTelemetry documentation.
Note that all configuration snippets are not complete configuration files, but only show the relevant sections for the sake of simplicity.

Structured logs

A structured log is a log whose textual format follows a consistent, machine-readable format. For applications, one of the most common formats is JSON.

OpenTelemetry format

If the application is already using the OpenTelemetry format for logs, OWASPLogger will make sure the OWASP information is in the correct place. Assuming you have a dedicated file for these JSON logs, you can use the filelog receiver to parse them:

receivers:
  filelog:
    include:
      - /path/to/your/file.log

processors:
  # Parse the OTel format from the JSON log lines in the file
  transform/parse-json:
    log_statements:
      - context: log
        error_mode: ignore  # Log the error and continue (ignore|silent|propagate)
        statements:
          - merge_maps(log.cache, ParseJSON(log.body), "upsert") where IsMatch(log.body, "^\\{")
          - set(log.body, log.cache["body"])
          - set(log.severity_number, Int(log.cache["severity_number"]))
          - set(log.severity_text, log.cache["severity_text"])
          - merge_maps(log.attributes, log.cache["attributes"], "upsert")
          - set(log.dropped_attributes_count, Int(log.cache["dropped_attributes"]))
          - set(log.time, Time(log.cache["timestamp"], "%FT%T.%fZ"))
          - merge_maps(resource.attributes, log.cache["resource"]["attributes"], "upsert")

service:
  pipelines:
    logs:
      receivers: [filelog]
      processors: [transform/parse-json]

More information on the OpenTelemetry log data model can be found here:

Generic JSON

If you have generic, arbitrarily-nested JSON logs in a file, you'll have to configure the transform processor accordingly in order to parse the relevant information.

transform/parse-json:
  log_statements:
    - context: log
      statements:
        - merge_maps(log.cache, ParseJSON(log.body), "upsert") where IsMatch(log.body, "^\\{")
        - # parse the relevant fields as needed

Semistructured Logs

A semistructured log is a log that does use some self-consistent patterns to distinguish data so that it’s machine-readable, but may not use the same formatting and delimiters between data across different systems. One example of this is juju debug-log.

You have to add some custom processors to the OpenTelemetry Collector configuration to parse the OWASP json blob from your logs.

[!NOTE] How do I configure custom processors?
In the charm, use the custom_processors Juju config option. In the snap, add the processors config under the processors: top-level key, then add the processor name in the related logs pipeline.

juju debug-log

juju debug-log embeds the OWASP event blob as a JSON in the Juju logs. Parsing its values into attributes is simple:

transform/parse-juju:
  log_statements:
    - context: log
      error_mode: ignore  # switch to ignore
      statements:
        - merge_maps(log.cache, ExtractPatterns(body, "^(?P<prefix>.*?)(?P<json>\\{.*\\}?)$"), "upsert")
        - merge_maps(log.attributes, ParseJSON(log.cache["json"]), "upsert")

Unstructured logs

Unstructured logs don't follow a consistent structure, and are thus more difficult to parse and analyze at scale. You need to write your own custom parsing logic in some processor in order to extract the attributes and fields you need.


📊 Visualizing OWASP Logs in Grafana

When OWASP logs reach Loki via the OpenTelemetry Collector used as a normalization point, they will contain the owasp_event as structured metadata. Looking at the data via the Grafana datasource UI, this is how an example OWASP log looks like:

2025-10-10_10-25

Here is an example query to count OWASP events by type:

sum by (owasp_event_name) (label_replace(count_over_time({service_name="example-service"}[$__auto] | owasp_event_event=~".+"), "owasp_event_name", "$1", "owasp_event_event", "^([^:]+):.*$"))

Using this query in a Grafana dashboard allows us to build a nice panel:

2025-10-10_11-44

🔗 References

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

owasp_logger-0.1.0.tar.gz (10.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

owasp_logger-0.1.0-py3-none-any.whl (11.2 kB view details)

Uploaded Python 3

File details

Details for the file owasp_logger-0.1.0.tar.gz.

File metadata

  • Download URL: owasp_logger-0.1.0.tar.gz
  • Upload date:
  • Size: 10.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.9.2

File hashes

Hashes for owasp_logger-0.1.0.tar.gz
Algorithm Hash digest
SHA256 ae99e72c834afff1c88defe7f7e84b82d9dbb7e9d7e4372decac543711585317
MD5 1e4892a95f05b9b2d8f201bd3ba2e80c
BLAKE2b-256 ed961c8c505cbd7645ab1cf95c1a6e873c41fc644e49e32f3c66da95c3da9143

See more details on using hashes here.

File details

Details for the file owasp_logger-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for owasp_logger-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0f6b48dfe76c1ec3716461577d76875488e57781e1874c1436ca89683a475742
MD5 bbe5edf68c6eb047a278bc3fc6fb50b3
BLAKE2b-256 19bd3fb78a8bad06198778719636d97fcc9422ecc9f290d7f1c3258447751e7c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page