Hi all,
I’ve run into an issue with only certain logs showing in docker-compose logs and was curious if someone here has had a similar experience.
Webserver logs work as expected (both gunicorn and default ‘manage.py runserver’) and continue to work properly as the container runs.
Any logs messages put in settings.py also show properly during initial startup of web server (and also show when I attempt to run a script).
After that, any log messages that I attempt to output via scripts (using django-extensions runscript) print to the console, but don’t show in docker-compose logs. In order to run these scripts, I’ll run an exec command like
docker-compose exec web python manage.py runscript scriptname
I have also tried abstracting away docker-compose, but the issue exists if I just use a Dockerfile and do a docker run/build. The behavior makes it seems as though unless Docker is explicitly responsible for the output of the logs via a command in docker-compose.yml, the logs won’t make it to the docker logs (this may just be a perceived cause).
I’ve tried a few logging settings within settings.py, including explicitly defining the console handler stream as sys.stdout or sys.stderr, but none have worked so far. The latest settings I’m using are below.
Logging to docker logs is desirable in order to get logs to show in kubernetes/AWS logging systems.
I would really appreciate any help if someone has any ideas. Been stuck on this one a while.
settings.py
import os
import logging.config
import sys
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
ENVIRONMENT = os.environ.get('ENVIRONMENT', default='production')
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ.get('SECRET_KEY')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = int(os.environ.get('DEBUG', default=0))
ALLOWED_HOSTS = ['*']
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'whitenoise.runserver_nostatic',
'django.contrib.staticfiles',
'django.contrib.sites',
# Third Party
'allauth',
'allauth.account',
'allauth.socialaccount',
'allauth.socialaccount.providers.openid',
'django_extensions',
# Local
'users.apps.UsersConfig',
'pages.apps.PagesConfig',
'meraki_rosie.apps.MerakiRosieConfig',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'rosie.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'rosie.wsgi.application'
# Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ.get('DB_NAME'),
'USER': os.environ.get('DB_USER'),
'PASSWORD': os.environ.get('DB_PASSWORD'),
'HOST': os.environ.get('DB_HOST'),
'PORT': 5432
}
}
# Password validation
# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/3.0/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.0/howto/static-files/
STATIC_URL = '/static/'
# Static files dev directory
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static'),]
# Static files prod directory
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATICFILES_FINDERS = [
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
]
# Set custom user model
AUTH_USER_MODEL = 'users.CustomUser'
# django-allauth config
LOGIN_REDIRECT_URL = 'home'
ACCOUNT_LOGOUT_URL = 'home'
SITE_ID = 1
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'allauth.account.auth_backends.AuthenticationBackend',
)
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
ACCOUNT_SESSION_REMEMBER = True
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_AUTHENTICATION_METHOD = 'email'
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_UNIQUE_EMAIL = True
# Production security settings
if ENVIRONMENT == 'production':
# Cross site scripting
SECURE_BROWSER_XSS_FILTER = True
# Force HTTPS
SECURE_SSL_REDIRECT = True
# HSTS
SECURE_HSTS_SECONDS = 3600
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_CONTENT_TYPE_NOSNIFF = True
# Secure cookies
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
# Referrer policy header
SECURE_REFERRER_POLICY = 'origin-when-cross-origin'
# Proxy header
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
# Logging Configuration
# Clear prev config
LOGGING_CONFIG = None
# Get loglevel from env
LOGLEVEL = os.getenv('DJANGO_LOGLEVEL', 'info').upper()
logging.config.dictConfig({
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'console': {
'format': '%(asctime)s %(levelname)s [%(name)s:%(lineno)s] %(module)s %(process)d %(thread)d %(message)s',
},
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'formatter': 'console',
},
},
'loggers': {
'': {
'level': LOGLEVEL,
'handlers': ['console',],
},
},
})
logger = logging.getLogger(__name__)
# These show in docker-compose logs
logging.critical('hello!')
logger.critical('hello again!')
docker-compose.yml
version: '3.7'
services:
web:
build: .
# command: python /rosie/manage.py runserver 0.0.0.0:8000
command: gunicorn rosie.wsgi -b 0.0.0.0:8000 --log-level=debug
env_file: .env-dev
volumes:
- .:/rosie
ports:
- 8000:8000
docker-compose.yml
Dockerfile
# Pull base image
FROM python:3.8.1
# Set environment variables, console buffering and prevents python cache files being written
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
# Set work directory
WORKDIR /rosie
VOLUME .:/rosie
# Install dependencies
COPY Pipfile Pipfile.lock /rosie/
RUN pip install pipenv && pipenv install --system
# Copy local project files to container
COPY . /rosie/
Sample Script
import logging
logger = logging.getLogger(__name__)
# prints to console, but doesn't show in docker-compose logs
logger.critical('this doesnt print')
logging.critical('nor does this')
def run():
# same as above
logger.critical('nor this')
logger.critical('nor this')
Console Output
$ docker-compose exec web python manage.py runscript sample_script
2020-04-07 18:11:11,162 CRITICAL [root:294] settings 8 140175221380928 hello!
2020-04-07 18:11:11,162 CRITICAL [rosie.settings:295] settings 8 140175221380928 hello again!
2020-04-07 18:11:11,686 CRITICAL [meraki_rosie.scripts.sample_script:6] sample_script 8 140175221380928 this doesnt print
2020-04-07 18:11:11,686 CRITICAL [root:7] sample_script 8 140175221380928 nor does this
2020-04-07 18:11:11,686 CRITICAL [meraki_rosie.scripts.sample_script:12] sample_script 8 140175221380928 nor this
2020-04-07 18:11:11,686 CRITICAL [meraki_rosie.scripts.sample_script:13] sample_script 8 140175221380928 nor this
2020-04-07 18:11:11,686 DEBUG [asyncio:59] selector_events 8 140175221380928 Using selector: EpollSelector
docker-compose logs
Attaching to rosie_web_1
web_1 | [2020-04-07 18:10:59 +0000] [1] [DEBUG] Current configuration:
web_1 | config: None
web_1 | bind: ['0.0.0.0:8000']
web_1 | backlog: 2048
web_1 | workers: 1
web_1 | worker_class: sync
web_1 | threads: 1
web_1 | worker_connections: 1000
web_1 | max_requests: 0
web_1 | max_requests_jitter: 0
web_1 | timeout: 30
web_1 | graceful_timeout: 30
web_1 | keepalive: 2
web_1 | limit_request_line: 4094
web_1 | limit_request_fields: 100
web_1 | limit_request_field_size: 8190
web_1 | reload: False
web_1 | reload_engine: auto
web_1 | reload_extra_files: []
web_1 | spew: False
web_1 | check_config: False
web_1 | preload_app: False
web_1 | sendfile: None
web_1 | reuse_port: False
web_1 | chdir: /rosie
web_1 | daemon: False
web_1 | raw_env: []
web_1 | pidfile: None
web_1 | worker_tmp_dir: None
web_1 | user: 0
web_1 | group: 0
web_1 | umask: 0
web_1 | initgroups: False
web_1 | tmp_upload_dir: None
web_1 | secure_scheme_headers: {'X-FORWARDED-PROTOCOL': 'ssl', 'X-FORWARDED-PROTO': 'https', 'X-FORWARDED-SSL': 'on'}
web_1 | forwarded_allow_ips: ['127.0.0.1']
web_1 | accesslog: None
web_1 | disable_redirect_access_to_syslog: False
web_1 | access_log_format: %(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"
web_1 | errorlog: -
web_1 | loglevel: debug
web_1 | capture_output: False
web_1 | logger_class: gunicorn.glogging.Logger
web_1 | logconfig: None
web_1 | logconfig_dict: {}
web_1 | syslog_addr: udp://localhost:514
web_1 | syslog: False
web_1 | syslog_prefix: None
web_1 | syslog_facility: user
web_1 | enable_stdio_inheritance: False
web_1 | statsd_host: None
web_1 | dogstatsd_tags:
web_1 | statsd_prefix:
web_1 | proc_name: None
web_1 | default_proc_name: rosie.wsgi
web_1 | pythonpath: None
web_1 | paste: None
web_1 | on_starting: <function OnStarting.on_starting at 0x7ff245b23dc0>
web_1 | on_reload: <function OnReload.on_reload at 0x7ff245b23ee0>
web_1 | when_ready: <function WhenReady.when_ready at 0x7ff245c3a040>
web_1 | pre_fork: <function Prefork.pre_fork at 0x7ff245c3a160>
web_1 | post_fork: <function Postfork.post_fork at 0x7ff245c3a280>
web_1 | post_worker_init: <function PostWorkerInit.post_worker_init at 0x7ff245c3a3a0>
web_1 | worker_int: <function WorkerInt.worker_int at 0x7ff245c3a4c0>
web_1 | worker_abort: <function WorkerAbort.worker_abort at 0x7ff245c3a5e0>
web_1 | pre_exec: <function PreExec.pre_exec at 0x7ff245c3a700>
web_1 | pre_request: <function PreRequest.pre_request at 0x7ff245c3a820>
web_1 | post_request: <function PostRequest.post_request at 0x7ff245c3a8b0>
web_1 | child_exit: <function ChildExit.child_exit at 0x7ff245c3a9d0>
web_1 | worker_exit: <function WorkerExit.worker_exit at 0x7ff245c3aaf0>
web_1 | nworkers_changed: <function NumWorkersChanged.nworkers_changed at 0x7ff245c3ac10>
web_1 | on_exit: <function OnExit.on_exit at 0x7ff245c3ad30>
web_1 | proxy_protocol: False
web_1 | proxy_allow_ips: ['127.0.0.1']
web_1 | keyfile: None
web_1 | certfile: None
web_1 | ssl_version: 2
web_1 | cert_reqs: 0
web_1 | ca_certs: None
web_1 | suppress_ragged_eofs: True
web_1 | do_handshake_on_connect: False
web_1 | ciphers: None
web_1 | raw_paste_global_conf: []
web_1 | strip_header_spaces: False
web_1 | [2020-04-07 18:10:59 +0000] [1] [INFO] Starting gunicorn 20.0.4
web_1 | [2020-04-07 18:10:59 +0000] [1] [DEBUG] Arbiter booted
web_1 | [2020-04-07 18:10:59 +0000] [1] [INFO] Listening at: http://0.0.0.0:8000 (1)
web_1 | [2020-04-07 18:10:59 +0000] [1] [INFO] Using worker: sync
web_1 | [2020-04-07 18:10:59 +0000] [7] [INFO] Booting worker with pid: 7
web_1 | [2020-04-07 18:10:59 +0000] [1] [DEBUG] 1 workers
web_1 | 2020-04-07 18:10:59,948 CRITICAL [root:294] settings 7 140678539028288 hello!
web_1 | 2020-04-07 18:10:59,948 CRITICAL [rosie.settings:295] settings 7 140678539028288 hello again!