diff --git a/Pipfile b/Pipfile index 84a410f51..3c6183e73 100644 --- a/Pipfile +++ b/Pipfile @@ -15,7 +15,7 @@ django-filter = "*" djangorestframework = "*" factory-boy = "*" filemagic = "*" -fuzzywuzzy = {extras = ["speedup"], version = "==0.15.0"} +fuzzywuzzy = {extras = ["speedup"],version = "==0.15.0"} gunicorn = "*" inotify-simple = "*" langdetect = "*" @@ -36,6 +36,7 @@ pytest-env = "*" pytest-xdist = "*" psycopg2 = "*" djangoql = "*" +pyyaml = "*" [dev-packages] ipython = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 29032f6bb..e7d734e49 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "971e0c975821925652865e34eed1c668bc6f52bb8217b776f35e87a66c936e1b" + "sha256": "e7f27d1fd7e19c75e9886914f3ef724c026d944b415768400d4274951a8ea8fc" }, "pipfile-spec": 6, "requires": {}, @@ -141,11 +141,11 @@ }, "django-extensions": { "hashes": [ - "sha256:8317a3fe479b1ba3e3a04ecf33fb8d6ccf09bb18f30eab64e34c40a593741d26", - "sha256:a76a61566f1c8d96acc7bcf765080b8e91367a25a2c6f8c5bddd574493839180" + "sha256:6fcedb2ea660c9dbf9ac59441721ffdd4ab5b753fbd6159c3e28f391a65bab46", + "sha256:a607459e5fa8c579a672131b63366fa52fab80adb2a862d362f5fb48cd2d2cac" ], "index": "pypi", - "version": "==2.1.4" + "version": "==2.1.5" }, "django-filter": { "hashes": [ @@ -424,11 +424,11 @@ }, "pycodestyle": { "hashes": [ - "sha256:cbc619d09254895b0d12c2c691e237b2e91e9b2ecf5e84c26b35400f93dcfb83", - "sha256:cbfca99bd594a10f674d0cd97a3d802a1fdef635d4361e1a2658de47ed261e3a" + "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", + "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c" ], "index": "pypi", - "version": "==2.4.0" + "version": "==2.5.0" }, "pygments": { "hashes": [ @@ -499,11 +499,11 @@ }, "pytest-xdist": { "hashes": [ - "sha256:107e9db0ee30ead02ca93e7d6d4846675f1b2142234f0eb1cd4d76739cd9ae6f", - "sha256:5795f665e112520fa5beab736ad957e7f36ce7d44210f4004be9d99f86529d97" + "sha256:4a201bb3ee60f5dd6bb40c5209d4e491cecc4d5bafd656cfb10f86178786e568", + "sha256:d03d1ff1b008458ed04fa73e642d840ac69b4107c168e06b71037c62d7813dd4" ], "index": "pypi", - "version": "==1.26.0" + "version": "==1.26.1" }, "python-dateutil": { "hashes": [ @@ -543,6 +543,23 @@ "index": "pypi", "version": "==2018.9" }, + "pyyaml": { + "hashes": [ + "sha256:3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b", + "sha256:3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf", + "sha256:40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a", + "sha256:558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3", + "sha256:a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1", + "sha256:aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1", + "sha256:bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613", + "sha256:d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04", + "sha256:d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f", + "sha256:e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537", + "sha256:e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531" + ], + "index": "pypi", + "version": "==3.13" + }, "regex": { "hashes": [ "sha256:0bcd8ab8c812278981df3161db3f94f0ec72f1fa07020173c96f20e74bd7c16a", @@ -703,11 +720,11 @@ }, "prompt-toolkit": { "hashes": [ - "sha256:c1d6aff5252ab2ef391c2fe498ed8c088066f66bc64a8d5c095bbf795d9fec34", - "sha256:d4c47f79b635a0e70b84fdb97ebd9a274203706b1ee5ed44c10da62755cf3ec9", - "sha256:fd17048d8335c1e6d5ee403c3569953ba3eb8555d710bfc548faf0712666ea39" + "sha256:88002cc618cacfda8760c4539e76c3b3f148ecdb7035a3d422c7ecdc90c2a3ba", + "sha256:c6655a12e9b08edb8cf5aeab4815fd1e1bdea4ad73d3bbf269cf2e0c4eb75d5e", + "sha256:df5835fb8f417aa55e5cafadbaeb0cf630a1e824aad16989f9f0493e679ec010" ], - "version": "==2.0.7" + "version": "==2.0.8" }, "ptyprocess": { "hashes": [ diff --git a/docs/index.rst b/docs/index.rst index fd9d57d4e..8b355d847 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -37,6 +37,7 @@ Contents setup consumption api + logging utilities guesswork migrating diff --git a/docs/logging.rst b/docs/logging.rst new file mode 100644 index 000000000..9f3bbe285 --- /dev/null +++ b/docs/logging.rst @@ -0,0 +1,100 @@ +.. _logging: + +Logging +======= + +By default paperless logs to standard output and to the database. Logs can be browsed from the paperless web interface in the ``Log`` section. + +Sentry +------ + +Paperless can be configured to send errors to the `sentry.io`_ error tracking software by setting the ``PAPERLESS_SENTRY_DSN`` value in ``paperless.conf``. + +.. _sentry.io: https://sentry.io/welcome/ + +Custom logger +------------- + +Paperless support customizing the logging configuration by using a ``/etc/paperless/logging.yml`` file. This file should be in the yaml format and is passed without modification to the python logging ``dictConfig``. For more information +about the format see: + + - `Django logging documentation`_ + - `Python logging dictConfig documentation`_ + +.. _Django logging documentation: https://docs.djangoproject.com/en/2.1/topics/logging/ +.. _Python logging dictConfig documentation: https://docs.python.org/3/library/logging.config.html#logging-config-dictschema + +Examples +-------- + +Default ++++++++ + +.. code-block:: yaml + + version: 1 + disable_existing_loggers: False + handlers: + consumer: + class: documents.loggers.PaperlessLogger + loggers: + documents: + handlers: + - consumer + level: INFO + +Send errors by email +++++++++++++++++++++ + +.. code-block:: yaml + + version: 1 + disable_existing_loggers: False + handlers: + consumer: + class: documents.loggers.PaperlessLogger + email: + class: logging.handlers.SMTPHandler + mailhost: localhost + fromaddr: paperless@example.com + toaddrs: + - foo@bar.com + subject: oops! + credentials: + - root + - hunter2 + loggers: + documents: + propagate: true + handlers: + - consumer + level: INFO + root: + handlers: + - email + level: ERROR + +Log debug to a file ++++++++++++++++++++ + +.. code-block:: yaml + + version: 1 + disable_existing_loggers: False + handlers: + consumer: + class: documents.loggers.PaperlessLogger + file: + class: logging.handlers.RotatingFileHandler + filename: paperless.log + maxBytes: 100000000 + loggers: + documents: + propagate: true + handlers: + - consumer + level: INFO + root: + handlers: + - file + level: DEBUG diff --git a/docs/migrating.rst b/docs/migrating.rst index 2c9e91897..c3e702bd5 100644 --- a/docs/migrating.rst +++ b/docs/migrating.rst @@ -92,7 +92,7 @@ files, the ``migrate`` step may not update anything. This is totally normal. Additionally, as new features are added, the ability to control those features is typically added by way of an environment variable set in ``paperless.conf``. You may want to take a look at the ``paperless.conf.example`` file to see if -there's anything new in there compared to what you've got int ``/etc``. +there's anything new in there compared to what you've got in ``/etc``. If you are :ref:`using Docker ` the update process is similar: diff --git a/src/paperless/settings.py b/src/paperless/settings.py index eee727287..6c1b7291c 100644 --- a/src/paperless/settings.py +++ b/src/paperless/settings.py @@ -11,6 +11,7 @@ """ import os +import yaml from dotenv import load_dotenv @@ -18,8 +19,12 @@ # Tap paperless.conf if it's available if os.path.exists("/etc/paperless.conf"): load_dotenv("/etc/paperless.conf") +elif os.path.exists("/etc/paperless/paperless.conf"): + load_dotenv("/etc/paperless/paperless.conf") elif os.path.exists("/usr/local/etc/paperless.conf"): load_dotenv("/usr/local/etc/paperless.conf") +elif os.path.exists("/usr/local/etc/paperless/paperless.conf"): + load_dotenv("/usr/local/etc/paperless/paperless.conf") def __get_boolean(key, default="NO"): @@ -222,22 +227,48 @@ def __get_boolean(key, default="NO"): # Logging -LOGGING = { - "version": 1, - "disable_existing_loggers": False, - "handlers": { - "consumer": { - "class": "documents.loggers.PaperlessLogger", - } - }, - "loggers": { - "documents": { - "handlers": ["consumer"], - "level": os.getenv("PAPERLESS_CONSUMER_LOG_LEVEL", "INFO"), +# Django use the python dictConfig format for logging. For more info See: +# * https://docs.djangoproject.com/en/2.1/topics/logging/ +# * https://docs.python.org/3/library/logging.config.html#logging-config-dictschema +# +# Removing the default logging configuration will disable logging into the database +# and the log view from the interface + +if os.path.exists("/etc/paperless/logging.yml"): + with open("/etc/paperless/logging.yml", 'r') as config: + LOGGING = yaml.load(config) +elif os.path.exists("/usr/local/etc/paperless/logging.yml"): + with open("/usr/local/etc/paperless/logging.yml", 'r') as config: + LOGGING = yaml.load(config) +else: + LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "handlers": { + "consumer": { + "class": "documents.loggers.PaperlessLogger", + } + }, + "loggers": { + "documents": { + "handlers": ["consumer"], + "level": os.getenv("PAPERLESS_CONSUMER_LOG_LEVEL", "INFO"), }, }, } +if os.getenv("PAPERLESS_SENTRY_DSN"): + import sentry_sdk + from sentry_sdk.integrations.django import DjangoIntegration + + sentry_logging = LoggingIntegration( + level=logging.INFO, # Capture info and above as breadcrumbs + event_level=logging.ERROR # Send errors as events + ) + sentry_sdk.init( + dsn=os.getenv("PAPERLESS_SENTRY_DSN"), + integrations=[sentry_logging, DjangoIntegration()] + ) # The default language that tesseract will attempt to use when parsing # documents. It should be a 3-letter language code consistent with ISO 639.