> ## Documentation Index
> Fetch the complete documentation index at: https://docs.appsignal.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Logging from Python

export const VersionRequirements = ({versions = []}) => {
  if (!Array.isArray(versions) || versions.length === 0) {
    return null;
  }
  const boundaries = {
    upper: " or lower",
    exact: "",
    lower: " or higher"
  };
  const containerStyle = {
    marginTop: "0.5rem",
    marginBottom: "1.5rem"
  };
  const lineStyle = {
    display: "block",
    margin: "0 0 0.35rem 0",
    fontSize: "0.875rem",
    lineHeight: "1.4"
  };
  const pillBaseStyle = {
    display: "inline-block",
    padding: "0.1em 0.4em",
    borderRadius: "0.25rem",
    border: "1px solid",
    fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
    fontSize: "0.85em",
    fontWeight: 500
  };
  const pillColors = {
    "AppSignal for Elixir": {
      background: "#f3e8ff",
      borderColor: "#e9d5ff",
      color: "#9333ea"
    },
    "AppSignal for Front-end": {
      background: "#fef9c3",
      borderColor: "#fde68a",
      color: "#ca8a04"
    },
    "AppSignal for Go": {
      background: "#ccfbf1",
      borderColor: "#99f6e4",
      color: "#0d9488"
    },
    "AppSignal for JavaScript": {
      background: "#fef9c3",
      borderColor: "#fde68a",
      color: "#ca8a04"
    },
    "AppSignal for Node.js": {
      background: "#dcfce7",
      borderColor: "#bbf7d0",
      color: "#16a34a"
    },
    "AppSignal for Python": {
      background: "#dbeafe",
      borderColor: "#bfdbfe",
      color: "#2563eb"
    },
    "AppSignal for Ruby": {
      background: "#fee2e2",
      borderColor: "#fecaca",
      color: "#dc2626"
    },
    "AppSignal for Rust": {
      background: "#ffedd5",
      borderColor: "#fed7aa",
      color: "#ea580c"
    }
  };
  const defaultPillColor = {
    background: "#f4f4f5",
    borderColor: "#e4e4e7",
    color: "#52525b"
  };
  const getPillStyle = name => ({
    ...pillBaseStyle,
    ...pillColors[name] || defaultPillColor
  });
  const getBoundText = bound => {
    return boundaries[bound] || boundaries.lower;
  };
  return <div style={containerStyle}>
      {versions.map((v, i) => <p key={`${v.name}-${v.version}-${v.bound || "lower"}-${i}`} style={lineStyle}>
          This feature requires{" "}
          <code style={getPillStyle(v.name)}>{v.name}</code> version {v.version}
          {getBoundText(v.bound)}.
        </p>)}
    </div>;
};

<VersionRequirements
  versions={[
{ name: "AppSignal for Python", version: "1.6.0" }
]}
/>

This documentation outlines how to configure logging with the AppSignal for Python integration.

<Warning>
  Logging is only available when using [collector
  mode](/python/configuration/collector), which is an **experimental
  feature**.
</Warning>

## Configure Logging

<Warning>
  🔐 Do not send <strong>Personal Identifiable Information (PII)</strong> to AppSignal. Filter PII (e.g., names, emails) from logs and use an ID, hash, or pseudonymized identifier instead. <br /> <br /> For **HIPAA-covered entities**, more information about signing a Business Associate Agreement (BAA) can be found in our [Business Add-Ons documentation](/support/business-add-ons).
</Warning>

You do not need to create a log source to send logs from the AppSignal for Python integration. An "application" log source will be created automatically.

When using [collector mode](/python/configuration/collector), AppSignal automatically instruments Python's built-in `logging` module via OpenTelemetry, attaching a handler to the root logger.

### Disabling logging instrumentation

Instrumentation for Python's `logging` module can be disabled using the [`disable_default_instrumentations` configuration option](/python/configuration/options#option-disable_default_instrumentations):

<CodeGroup>
  ```python Python theme={null}
  # __appsignal__.py
  appsignal = Appsignal(
      # ...
      disable_default_instrumentations=["logging"],
  )
  ```
</CodeGroup>

This disables the automatic instrumentation of the `logging` module by attaching a handler to the root logger, meaning that logs sent to Python's `logging` module will no longer be captured by AppSignal.

You can still send logs directly using the OpenTelemetry logs API, as described in the [Sending Logs with OpenTelemetry](#sending-logs-with-opentelemetry) section below.

#### Attaching handlers manually

When automatic instrumentation is disabled, you can attach the OpenTelemetry `LoggingHandler` to specific loggers yourself, rather than relying on AppSignal adding it to the root logger:

<CodeGroup>
  ```python Python theme={null}
  import logging
  from opentelemetry.sdk._logs import LoggingHandler

  handler = LoggingHandler(level=logging.NOTSET)
  logging.getLogger("myapp").addHandler(handler)
  ```
</CodeGroup>

This gives you control over which loggers send their output to AppSignal.

### Log level

Python's `logging` module defaults to `WARNING`, meaning `INFO` and `DEBUG` messages are not captured by default. To capture lower-severity messages, set the root logger level explicitly:

<CodeGroup>
  ```python Python theme={null}
  import logging
  logging.root.setLevel(logging.INFO)
  ```
</CodeGroup>

### Which logs are captured

AppSignal captures logs sent to the root logger and any logger that propagates to it. Propagation is controlled by the `propagate` attribute on each logger, which defaults to `True` in Python.

Loggers with `propagate=False` will **not** have their messages captured by AppSignal.

## Framework Setup

### Django

Django configures some loggers with `propagate=False` by default, which prevents them from reaching AppSignal. Override these in `settings.py` to re-enable propagation:

<CodeGroup>
  ```python Python theme={null}
  # settings.py
  LOGGING = {
      "version": 1,
      "disable_existing_loggers": False,
      "handlers": {
          "console": {"class": "logging.StreamHandler"},
      },
      "loggers": {
          "django.server": {
              "handlers": ["console"],
              "level": "INFO",
              "propagate": True,
          },
          "myapp": {
              "handlers": ["console"],
              "level": "INFO",
              "propagate": True,
          },
      },
  }
  ```
</CodeGroup>

Setting `"propagate": True` ensures that the logger's messages reach the root logger, where AppSignal's handler is attached.

#### Attaching handlers manually

Alternatively, you can attach the `LoggingHandler` directly to Django's loggers instead of relying on propagation to the root logger:

<CodeGroup>
  ```python Python theme={null}
  # settings.py
  import logging
  from opentelemetry.sdk._logs import LoggingHandler

  LOGGING = {
      "version": 1,
      "handlers": {
          "console": {"class": "logging.StreamHandler"},
          "appsignal": {"()": LoggingHandler, "level": logging.NOTSET},
      },
      "loggers": {
          "django.server": {
              "handlers": ["console", "appsignal"],
              "level": "INFO",
          },
          "myapp": {
              "handlers": ["console", "appsignal"],
              "level": "INFO",
          },
      },
  }
  ```
</CodeGroup>

### Celery

Celery forks worker processes, so AppSignal must be started for each fork. Use the `worker_process_init` signal to reinitialise AppSignal and configure logging in each worker process. See the [Celery instrumentation page](/python/instrumentations/celery) for more details on instrumenting Celery tasks.

<CodeGroup>
  ```python Python theme={null}
  # tasks.py
  import logging
  import appsignal
  from celery.signals import worker_process_init

  @worker_process_init.connect(weak=False)
  def init_worker_logging(*args, **kwargs):
      appsignal.start()
      logging.root.setLevel(logging.INFO)
      logging.getLogger("celery").propagate = True
  ```
</CodeGroup>

## Sending Logs with OpenTelemetry

You can also send logs directly using the OpenTelemetry logs API, without going through Python's `logging` module:

<CodeGroup>
  ```python Python theme={null}
  from opentelemetry._logs import get_logger_provider
  from opentelemetry.sdk._logs import SeverityNumber

  logger_provider = get_logger_provider()
  logger = logger_provider.get_logger("my-app")

  logger.emit(
      logger_provider.get_logger("my-app").create_log_record(
          body="Log message line",
          severity_number=SeverityNumber.INFO,
      )
  )
  ```
</CodeGroup>

## Need Help?

After configuring your Python application to send logs, logs should appear in AppSignal. If you are unsure about this step or AppSignal is not receiving any logs, you can always [reach out](mailto:support@appsignal.com) for assistance. We'll help get you back on track!
