Skip to main content

structguru

A native structured logging library with a loguru-style API.

Combines a loguru-style API — brace formatting, bind, contextualize, opt, sink management — with a native Rust renderer for maximum performance. Since v1.0, the Rust extension is the default (and only) rendering path; structlog and orjson are no longer dependencies.

Features

  • Loguru-style APIlogger.info("User {id} logged in", id=123)
  • Structured JSON output in production (rendered natively in Rust for speed)
  • Pretty colored console output in development
  • Context managementbind() for persistent context, contextualize() for request-scoped context
  • Sentry integration — redacted breadcrumbs/events with raw exceptions preserved for capture
  • stdlib interoplogger.add() sinks can also receive third-party logging records
  • RFC 5424 severity codes included in every log record
  • Native Rust runtime — rendering and output run through the bundled abi3 extension
  • Fully typed — PEP 561 compliant with strict mypy

Native processing:

  • Redaction — mask sensitive fields (passwords, tokens) by key name or regex
  • Sampling — probabilistic and rate-limited log suppression
  • Metrics — extract counters/histograms from log events via callbacks
  • Exception formatting — render exc_info as text or a structured frame dictionary
  • Off-thread logging — native Rust writer with a bounded queue and backpressure
  • OpenTelemetry — automatic trace_id/span_id injection from current span

Framework integrations (optional dependencies):

  • ASGI (FastAPI, Starlette) — request ID, timing, context binding middleware
  • Celery — task context binding and cross-worker context propagation via headers
  • Flask — before/after request hooks with request ID tracking
  • Django — logging dict config builder and request middleware
  • SQLAlchemy — slow query detection and logging
  • gRPC — server interceptor with per-RPC context binding
  • Sentry — forward log events as breadcrumbs/events with configurable severity

Installation

pip install structguru

With optional integrations:

pip install structguru[celery,flask,sentry]  # pick what you need
pip install structguru[all]                   # everything

Available extras: otel, celery, flask, django, sqlalchemy, grpc, sentry, httpx, requests, all.

Quick start

from structguru import configure, logger

# Configure once at startup
configure(service="myapp", level="DEBUG", format="json")

# Use anywhere
logger.info("Hello {name}", name="world")
# → {"logger":"...","level":"INFO","severity":6,"timestamp":"...","service":"myapp","message":"Hello world"}

Usage

Log levels

logger.debug("Debug message")
logger.info("Info message")
logger.warning("Warning message")
logger.error("Error message")
logger.critical("Critical message")

# Aliases
logger.trace("Maps to DEBUG")
logger.success("Maps to INFO")
logger.warn("Alias for warning")
logger.fatal("Alias for critical")

Brace formatting

Arguments used in str.format placeholders are consumed by formatting (matching loguru behaviour). Extra kwargs that are not in any placeholder are forwarded as structured fields:

logger.info("User {user_id} logged in", user_id=42, ip="10.0.0.1")
# message: "User 42 logged in"
# ip: "10.0.0.1"  (extra kwarg kept as structured field)
# user_id is consumed by formatting and not duplicated

Bound context

log = logger.bind(request_id="abc-123", user="alice")
log.info("Processing request")  # includes request_id and user
log.info("Request complete")  # same context carried through

Request-scoped context

with logger.contextualize(request_id="abc-123"):
    logger.info("Handling request")  # includes request_id
    do_work()  # any logging inside also gets request_id
# request_id removed automatically

Exception logging

try:
    risky_operation()
except Exception:
    logger.exception("Operation failed")  # logs with exc_info at ERROR level

# Or with opt():
logger.opt(exception=True).error("Something went wrong")

Sink management

# Add a file sink
handler_id = logger.add("/var/log/app.log", level="ERROR")

# Add a callable sink
logger.add(lambda msg: send_to_monitoring(msg), level="CRITICAL")

# Remove a specific sink
logger.remove(handler_id)

# Remove all added sinks
logger.remove()

On Unix, new files created by either logger.add(path) or the native rotating file sink are owner-only (0600). Existing files retain their permissions.

All sink forms receive structguru records. They are also registered with the stdlib root logger for third-party records, which arrive raw (unrendered) on that path. Install the stdlib bridge to receive them rendered and redacted instead — while it is installed the raw delivery is suspended, so a sink never sees the same record twice.

Native delivery uses the bounded callable queue and is drained on reconfiguration, shutdown(), fork, and interpreter exit. Call structguru.flush() when you need to block until buffered records have actually been written:

import structguru

logger.info("checkpoint")
structguru.flush()  # returns once the line has reached its sink

Console vs JSON output

format= selects the renderer: "json" (default, production) or "console" (colored, human-readable development output).

# JSON (production)
configure(service="myapp", format="json")
# → {"logger":"...","level":"INFO","severity":6,"timestamp":"...","service":"myapp","message":"..."}

# Console (development) — colored, human-readable
configure(service="myapp", format="console")
# → 2026-01-15T12:00:00.123456Z [INFO    ] Hello world

Native processing

Redaction

Mask sensitive fields automatically:

from structguru import configure

configure(
    sensitive_keys=["password", "token", "ssn"],
    sensitive_patterns=[r"\b\d{3}-\d{2}-\d{4}\b"],
    pattern_replacement="***",
)

Patterns run on Rust's linear-time regex engine (no ReDoS), which rejects look-around and backreferences at configure() time. Most look-behinds rewrite as capture groups — (?<=password=)\S+ becomes (password=)\S+ with pattern_replacement="$1[REDACTED]", so the prefix is re-emitted and the secret is replaced (password=hunter2password=[REDACTED]). Put the capture group around the part you want to keep, never around the secret. For patterns that can't be rewritten, allow_backtracking_patterns=True opts them into a bounded backtracking engine: look-around and backreferences then work as written, at the cost of the linear-time guarantee for those patterns. If a value ever exceeds the backtrack limit, it is redacted entirely (fail-closed) rather than emitted unchecked.

configure(
    sensitive_patterns=[r"(?<=password=)\S+"],
    allow_backtracking_patterns=True,
)

Sampling & rate limiting

Suppress noisy logs:

from structguru import configure

configure(sample_rate=0.1, rate_limit_max=5, rate_limit_period=60)

Metric extraction

Derive metrics from log events:

from structguru import MetricProcessor, configure

metrics = MetricProcessor()
metrics.counter("user.login", lambda ed: login_counter.inc())
metrics.histogram("db.query", "duration_ms", lambda v, ed: query_hist.observe(v))

configure(metric_processor=metrics)

Exception formatting

Render exceptions as JSON-serializable dictionaries:

from structguru import configure

configure(structured_exceptions=True, exception_max_frames=20)

exception_max_frames=0 omits traceback frames entirely. Negative frame and local-representation limits are rejected during configuration.

OpenTelemetry correlation

Inject trace context into every log event:

from structguru import configure

configure(otel=True)  # no-op injection when opentelemetry-api is absent

Non-blocking logging

Since v1.0, log I/O is offloaded to a background thread by default. The native Rust writer uses a bounded 8192-record queue with lossless backpressure. Set overflow="drop" to favor caller latency, or explicitly pass maxsize=0 only when an unbounded queue is acceptable.

Native runtime

structguru ships a required Rust extension that renders and enqueues logging natively, off-thread. It is auto-enabled at import time. The runtime does not depend on orjson; exotic values (datetime, UUID, Enum, dataclasses) are converted natively in Rust.

A field value the renderer cannot represent never fails the record. It is replaced by a marker so the message, level, remaining fields, and exception traceback still ship:

  • <unsupported: WSGIRequest> for any other object (Decimal, bytes, set, Path, request objects, ...). The marker names the type only: structguru never calls str()/repr() on an arbitrary object or reads its attributes, so nothing it holds can leak into a log line.
  • <cycle: dict> for a container that refers back to itself.
  • <max depth exceeded> beyond 64 levels of nesting.

Integers outside the 64-bit range are written as JSON numbers, non-string mapping keys are rendered as strings ({200: 3} becomes {"200": 3}), and unpaired surrogates in text become U+FFFD. Markers are redacted like any other string. The policy applies to native logger fields and to extra= fields bridged from the standard library alike.

import structguru

# Native mode is already on. Logger calls route through the Rust renderer.
structguru.logger.info("order {id} accepted", id=987)
# → JSON line written to stdout by a background writer thread

No configuration is required for the default JSON-to-stdout behavior. Call configure(...) to customize the renderer, filtering, or sinks.

import structguru

structguru.configure(service="myapp", level="INFO", file_path="/var/log/app.log")
structguru.logger.bind(request_id="abc").info("order {id} accepted", id=987)
# → JSON line written to /var/log/app.log by a background writer thread

The default import-time configuration also honors environment variables:

LOG_LEVEL=INFO STRUCTGURU_SERVICE=myapp python -m myapp

Invalid native environment values fail import with an actionable exception. This prevents a deployment from starting while the native-only logging path is disabled.

Public API:

Symbol Purpose
configure(...) Configure rendering, filtering, redaction, and output sinks. See the API reference for the complete signature.
shutdown() Stop the writer; logging is disabled until configure() is called.
set_level(level) Adjust the level threshold at runtime.
writer_metrics() Writer counters (enqueued/written/dropped/depth/...) plus filter counters (sampled/rate_limited) when active.
is_available() Whether the compiled extension is importable.

Behavior notes:

  • Overflow: the default maxsize=8192 uses overflow="block" for bounded, lossless backpressure. Use overflow="drop" for drop-newest behavior with metrics and rate-limited warnings. maxsize=0 explicitly opts into an unbounded queue.
  • Redaction, level filtering, exceptions, and OpenTelemetry injection are supported natively; redaction covers the message and all structured string values before rendering or Sentry export. sensitive_keys overrides the default redaction keys. Rust's linear-time regex engine rejects backreferences and look-around with ValueError at configuration time.
  • Sampling & rate limiting (sample_rate, rate_limit_max, rate_limit_period) are applied as native pre-render filters — dropped records cost zero rendering. sampled and rate_limited counters are distinct from the transport dropped counter. sample_max_level restricts sampling to records at or below that level; more severe records always pass.
  • Metric hooks (metric_processor=...) invoke a structlog-style processor (e.g. MetricProcessor) for every kept record on the caller's thread, with (None, method, {"event": message, **fields}). Dropped records (level/sampling/rate-limit) never reach it; hook errors are swallowed.
  • Fork/shutdown safe — the writer is flushed on exit and respawned in forked children (gunicorn/celery prefork). Rotating-file writers sharing a path coordinate through an owner-only .lock sidecar; distributed hosts should still prefer stdout and an external collector.
  • Structured exceptions (structured_exceptions=True) render type, message, module, and frames as a dictionary, with optional redacted/truncated locals controlled by the exception_* options.
  • stack_info is supported natively: the stack is captured in Python and rendered in the same position as StackInfoRenderer (stack between service and message). Unlike the standard path, the stack ends at the user's calling frame (structguru-internal frames are skipped, the way structlog skips its own).
  • Console mode (format="console"): renders colored, human-readable lines instead of JSON — structguru's own stable dev format (<timestamp> [<LEVEL>] <message> k=v), with ANSI colors by default on a TTY. Override with colors=True/False.
  • File sinks (file_path=...): write to a rotating file natively. Defaults mirror RotatingFileHandler (50 MB, 5 backups); configure via file_max_bytes/file_backup_count. Set also_stdout=True to mirror output to both file and stdout (e.g. container + persistent log).
  • Callable sinks (callable_sinks=[fn, ...]): use a bounded queue (callable_queue_maxsize=1024). overflow="block" provides lossless backpressure; overflow="drop" reports callable_dropped metrics. Flush and lifecycle operations drain queued calls.
  • Sentry integration (sentry_processor=SentryProcessor(...)): receives the already-redacted event and raw exc_info only for exception capture.
  • Scope: the native renderer covers JSON and console rendering, file/stdout/callable sinks, redaction, sampling/rate limiting, metrics, exceptions, and stack information. logger.add() sinks receive native and stdlib records.

Framework integrations

ASGI (FastAPI / Starlette)

from structguru.integrations.asgi import StructguruMiddleware

app = FastAPI()
app.add_middleware(StructguruMiddleware, request_id_header="X-Request-ID")

Celery

from structguru.integrations.celery import setup_celery_logging

setup_celery_logging(propagate_context=True, context_keys=["request_id"])
# Binds task_id/task_name to context, propagates selected keys via headers

Flask

from structguru.integrations.flask import setup_flask_logging

app = Flask(__name__)
setup_flask_logging(app, request_id_header="X-Request-ID")

Django

# settings.py
from structguru.integrations.django import build_logging_config, StructguruMiddleware

LOGGING = build_logging_config(service="myapp", level="INFO", json_logs=True)
MIDDLEWARE = ["structguru.integrations.django.StructguruMiddleware", ...]

SQLAlchemy

from structguru.integrations.sqlalchemy import setup_query_logging

setup_query_logging(engine, slow_threshold_ms=100, log_all=False)

gRPC

from structguru.integrations.grpc import StructguruInterceptor

server = grpc.server(
    futures.ThreadPoolExecutor(),
    interceptors=[StructguruInterceptor()],
)

Sentry

import logging

from structguru import configure
from structguru.integrations.sentry import SentryProcessor

sentry = SentryProcessor(event_level=logging.ERROR, tag_keys=frozenset({"service"}))
configure(sentry_processor=sentry)

Stdlib bridge

Third-party libraries log through the standard logging module. Installing the bridge re-emits those records through structguru, so they share the same JSON / console formatting, redaction, and output stream as your own logs:

from structguru.integrations.stdlib import install_stdlib_bridge

bridge = install_stdlib_bridge(
    level="INFO",
    suppress_loggers=("urllib3", "botocore"),
    disable_existing_loggers=False,
)

import logging

logging.getLogger("sqlalchemy.engine").info("SELECT 1")
# → {"logger":"sqlalchemy.engine","level":"INFO",...,"message":"SELECT 1"}

While the bridge is installed, logger.add() sinks receive third-party records only through it — rendered once, never also raw. Pass the returned handler to uninstall_stdlib_bridge() to restore the previous behavior.

Installing a second bridge while one is active raises RuntimeError. When logging setup legitimately runs more than once per process (a Django manage.py that imports a Celery app module, repeated setup in test suites), pass replace=True to release the previous bridge first — last call wins:

bridge = install_stdlib_bridge(level="INFO", replace=True)

The swap is atomic for callers: a record logged by another thread during it is delivered at most once (rendered, raw, or dropped — never twice). Suppression levels applied by the earlier install are not reverted, and calling uninstall_stdlib_bridge() on the replaced handler is a no-op.

disable_existing_loggers=True disables named stdlib loggers that already exist at installation time; False re-enables them, following dictConfig semantics. When the option is omitted, install_stdlib_bridge() reads STRUCTGURU_STDLIB_DISABLE_EXISTING_LOGGERS; if the variable is also unset, existing states are preserved. Explicit Python values override the environment. An empty environment value is treated as unset.

STRUCTGURU_STDLIB_DISABLE_EXISTING_LOGGERS=false python -m myapp

To configure all bridge options from environment variables at a controlled point in application startup:

STRUCTGURU_STDLIB_LEVEL=INFO \
STRUCTGURU_STDLIB_DISABLE_EXISTING_LOGGERS=false \
python -m myapp
from structguru.integrations.stdlib import install_stdlib_bridge_from_env

bridge = install_stdlib_bridge_from_env()

Requirements

  • Python 3.11+
  • The compiled Rust extension (shipped as abi3 wheels for Linux/macOS/Windows)

Documentation & Examples

Development

uv sync --all-extras
uv run pytest
make bench
uv run ruff check .
uv run mypy src/

License

MIT — Copyright (c) 2025 Aleksandr Pavlov

Download files

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

Source Distribution

structguru-1.2.3.tar.gz (87.4 kB view details)

Uploaded Source

Built Distributions

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

structguru-1.2.3-cp311-abi3-win_amd64.whl (927.5 kB view details)

Uploaded CPython 3.11+Windows x86-64

structguru-1.2.3-cp311-abi3-musllinux_1_2_x86_64.whl (1.2 MB view details)

Uploaded CPython 3.11+musllinux: musl 1.2+ x86-64

structguru-1.2.3-cp311-abi3-musllinux_1_2_aarch64.whl (1.1 MB view details)

Uploaded CPython 3.11+musllinux: musl 1.2+ ARM64

structguru-1.2.3-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (991.6 kB view details)

Uploaded CPython 3.11+manylinux: glibc 2.17+ x86-64

structguru-1.2.3-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (955.4 kB view details)

Uploaded CPython 3.11+manylinux: glibc 2.17+ ARM64

structguru-1.2.3-cp311-abi3-macosx_11_0_arm64.whl (900.9 kB view details)

Uploaded CPython 3.11+macOS 11.0+ ARM64

structguru-1.2.3-cp311-abi3-macosx_10_12_x86_64.whl (936.2 kB view details)

Uploaded CPython 3.11+macOS 10.12+ x86-64

File details

Details for the file structguru-1.2.3.tar.gz.

File metadata

  • Download URL: structguru-1.2.3.tar.gz
  • Upload date:
  • Size: 87.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for structguru-1.2.3.tar.gz
Algorithm Hash digest
SHA256 fde9d325d6d6f0482bba0d872655f2d77104cd38eca63307e5da23f4118f6630
MD5 af21a67d22dfcb536d9a5b587ac45855
BLAKE2b-256 654a1cb3d903bf858bf647d70455ff0af78e54cef631e5c4c933e366169500ef

See more details on using hashes here.

Provenance

The following attestation bundles were made for structguru-1.2.3.tar.gz:

Publisher: wheels.yml on kidoz/structguru

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file structguru-1.2.3-cp311-abi3-win_amd64.whl.

File metadata

  • Download URL: structguru-1.2.3-cp311-abi3-win_amd64.whl
  • Upload date:
  • Size: 927.5 kB
  • Tags: CPython 3.11+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for structguru-1.2.3-cp311-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 a5f79a7aae0f17d472b653022225524f9351744eb3c43bf43150fe8523a7512d
MD5 2d179bab01cb7eefe6c0ac7f917cf171
BLAKE2b-256 b36586ab61a4fac24c194ab5fd951af69e546d7facd2a2e255afba3709c30ddd

See more details on using hashes here.

Provenance

The following attestation bundles were made for structguru-1.2.3-cp311-abi3-win_amd64.whl:

Publisher: wheels.yml on kidoz/structguru

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file structguru-1.2.3-cp311-abi3-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for structguru-1.2.3-cp311-abi3-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 25019a9ab14bccea6f84b41c9b1a030721026b4979badb0542ed67bd98fcbd8f
MD5 f977a21d2929197923c26b9ff79298e8
BLAKE2b-256 60c14952a1c76e4d1b1cba4577e97ebefb35c7d9ee0463d2799da20a97853d6d

See more details on using hashes here.

Provenance

The following attestation bundles were made for structguru-1.2.3-cp311-abi3-musllinux_1_2_x86_64.whl:

Publisher: wheels.yml on kidoz/structguru

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file structguru-1.2.3-cp311-abi3-musllinux_1_2_aarch64.whl.

File metadata

File hashes

Hashes for structguru-1.2.3-cp311-abi3-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 421bb29f65abd32b482258411e9e5fc4309bd60b0e04a49d397393e7474a93ad
MD5 9558ba9c61b1c599e14f1927469e1746
BLAKE2b-256 e2ecdd397748e79e3b6c406bbd9ee8b22cae2f22a54f3993b7137cd82652e3ad

See more details on using hashes here.

Provenance

The following attestation bundles were made for structguru-1.2.3-cp311-abi3-musllinux_1_2_aarch64.whl:

Publisher: wheels.yml on kidoz/structguru

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file structguru-1.2.3-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for structguru-1.2.3-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 59e91941e7794d45466d5c869a8d596f95f034322ea5a5900acdd15510344a72
MD5 f2014ef646471e81a1fbd00380e51c8e
BLAKE2b-256 2d994cad5b335a2da95fb68287f094d88d12d75a33b69a85c02156f1debbd457

See more details on using hashes here.

Provenance

The following attestation bundles were made for structguru-1.2.3-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: wheels.yml on kidoz/structguru

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file structguru-1.2.3-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for structguru-1.2.3-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 f3c1990d772686eca1ca8e4510dd0806d8431883528c0564510fdc48764140e0
MD5 b74dba2c17c3f7856bdc63f444c8dac2
BLAKE2b-256 55c72d5bdd008c74c86cd7c4153c7d94befe4f79f437a1cadeed50fc4e57fe3a

See more details on using hashes here.

Provenance

The following attestation bundles were made for structguru-1.2.3-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: wheels.yml on kidoz/structguru

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file structguru-1.2.3-cp311-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for structguru-1.2.3-cp311-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 666d6d8821497d63208f2b56bec67f5b794956e65ffbcbd5d5480168e7215d84
MD5 3fabfce77a5b2c0dfbd827574a579311
BLAKE2b-256 d5810b1c9dfa10c42df8676a36f581dc11be35216affbbfecdf9e8306a297acc

See more details on using hashes here.

Provenance

The following attestation bundles were made for structguru-1.2.3-cp311-abi3-macosx_11_0_arm64.whl:

Publisher: wheels.yml on kidoz/structguru

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file structguru-1.2.3-cp311-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for structguru-1.2.3-cp311-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 0f52a6840a2180cdf76aa3e14272e987b26a5b2b386b367dd7ddae6de8255b0a
MD5 8f376d449ba8667193b3b39909c2e114
BLAKE2b-256 f3bf60e1c1ddb359be1e3070a11e908c0f69697264ae7f536d2b36d6cd3dc147

See more details on using hashes here.

Provenance

The following attestation bundles were made for structguru-1.2.3-cp311-abi3-macosx_10_12_x86_64.whl:

Publisher: wheels.yml on kidoz/structguru

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

1.2.3 This release

8 files

1.2.2

8 files

1.2.1

8 files

1.2.0

8 files

1.1.0

8 files

1.0.6

8 files

1.0.5

8 files

1.0.4

8 files

1.0.3

8 files

1.0.2

8 files

0.2.0

2 files

0.1.1

2 files

0.1.0

2 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